SPRing AOP技術(基于aspectJ)的xml開發
@(Spring)[aop, spring, xml, Spring, annotation, aspectJ]
Spring AOP技術基于AspectJ的XML開發Spring AOP的XML的開發AOP的概述什么是AOPSpring中的AOPSpring的AOP的底層實現Spring的底層實現之JDK動態代理Spring的底層實現之CGLIB動態代理Spring的AOP的術語Spring的AOP開發分成兩類AOP開發中的術語Spring的基于AspectJ的AOP的XML開發第一步引入jar包第二步創建配置文件第三步創建需增強包和類第四步將類交給Spring管理第五步編寫測試第六步編寫切面類和通知第七步 配置切面類第八步通過配置實現AOP第九步執行測試切入點表達式的定義切入點表達式定義語法通知類型前置通知創建需增強類和方法創建切面和通知配置切面和通知單元測試后置通知創建需增強類和方法創建切面和通知配置切面和通知單元測試環繞通知創建需增強類和方法創建切面和通知配置切面和通知單元測試異常拋出通知創建需增強類和方法創建切面和通知配置切面和通知單元測試最終通知創建需增強類和方法創建切面和通知配置切面和通知單元測試Spring AOP小項目
Spring AOP的XML的開發
AOP的概述
什么是AOP
在軟件業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
將日志記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的代碼。
從最開始的面向機器編程到面向過程編程到面向對象對象編程直至現在的面向切面編程可以看出,隨著計算機軟件的不斷發展,其越來越符合人的思維習慣,同樣的代碼量所能實現的功能也越來越多。而AOP則是在不增業務邏輯的代碼上,增加新功能。而AOP一般用于框架開發。在實際項目中,使用AOP也是一個趨勢。
AOP面向切面編程,是OOP擴展和延伸,使用的是橫向抽取機制取代傳統的縱向繼承體系對程序進行擴展。解決OOP中遇到問題。
Spring中的AOP
Spring的AOP的底層實現
Spring提供兩種代理機制實現AOP JDK動態代理 :JDK只能對實現了接口的類產生代理。CGLIB動態代理   :可以對沒有實現接口的類產生代理,用的是底層字節碼增強技術。產生類繼承父類。Spring的底層實現之JDK動態代理
package com.pc.aop;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * JDK動態代理實現  * 需要提供類的接口 *  * @author Switch * @data 2016年11月23日 * @version V1.0 */public class JDKProxy<T> implements InvocationHandler {    // 持有需要被增強的對象的引用    T target;    /**     * 構造方法必須提供被代理對象     *      * @param target     */    public JDKProxy(T target) {        this.target = target;    }    /**     * 創建代理對象     *      * @return     */    public T createProxy() {        T proxy = (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),                this);        return proxy;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        // 增強,這里實現前置通知        System.out.println("前置通知。。。。。");        // 調用原有接口方法        return method.invoke(target, args);    }}package com.pc.test;import org.junit.Test;import com.pc.aop.JDKProxy;import com.pc.service.UserService;import com.pc.service.impl.UserServiceImpl;/** * 代理測試類 *  * @author Switch * @data 2016年11月23日 * @version V1.0 */public class ProxyTest {    @Test    public void testJDKProxy() {        // 創建被代理對象        UserService userService = new UserServiceImpl();        // 未增強之前的方法        // 輸出:保存用戶        userService.save();        JDKProxy<UserService> jdkProxy = new JDKProxy<>(userService);        // 創建代理對象        userService = jdkProxy.createProxy();        // 增強后的方法        // 輸出:        // 前置通知。。。。。        // 保存用戶        userService.save();    }}Spring的底層實現之CGLIB動態代理
package com.pc.aop;import java.lang.reflect.Method;import org.springframework.cglib.proxy.Enhancer;import org.springframework.cglib.proxy.MethodInterceptor;import org.springframework.cglib.proxy.MethodProxy;/** * CGLIB實現代理  * 使用字節碼增強技術,生成代理類的子類 *  * @author Switch * @data 2016年11月23日 * @version V1.0 */public class CGLIBProxy<T> implements MethodInterceptor {    // 持有需要被增強的對象的引用    T target;    /**     * 構造方法必須提供被代理對象     *      * @param target     */    public CGLIBProxy(T target) {        this.target = target;    }    /**     * 創建代理對象     * @return     */    public T createProxy() {        // 創建代理核心對象        Enhancer enhancer = new Enhancer();        // 設置父類        enhancer.setSuperclass(target.getClass());        // 設置回調        enhancer.setCallback(this);        // 創建代理對象        T proxy = (T) enhancer.create();        return proxy;    }    @Override    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {        // 增強,這里實現后置通知        // 調用原有類方法,獲得返回值        Object result = methodProxy.invokeSuper(proxy, args);        // 增強        System.out.println("后置通知。。。。。");        return result;    }}package com.pc.test;import org.junit.Test;import com.pc.aop.CGLIBProxy;import com.pc.service.UserService;import com.pc.service.impl.UserServiceImpl;/** * 代理測試類 *  * @author Switch * @data 2016年11月23日 * @version V1.0 */public class ProxyTest {    @Test    public void testCGLIBProxy() {        // 創建被代理對象        UserService userService = new UserServiceImpl();        // 未增強之前的方法        // 輸出:保存用戶        userService.save();        CGLIBProxy<UserService> cglibProxy = new CGLIBProxy<>(userService);        // 創建代理對象        userService = cglibProxy.createProxy();        // 增強后的方法        // 輸出:        // 保存用戶        // 后置通知。。。。。        userService.save();    }}Spring的AOP的術語
Spring的AOP開發分成兩類
傳統AOP開發基于AspectJ的AOP的開發(XML和注解)AOP開發中的術語

切面(aspect):要實現的交叉功能,是系統模塊化的一個切面或領域。如日志記錄。連接點(join point):應用程序執行過程中插入切面的地點,可以是方法調用,異常拋出,或者要修改的字段。通知(advice):切面的實際實現,它通知系統新的行為。如在日志通知包含了實現日志功能的代碼,如向日志文件寫日志。通知在連接點插入到應用系統中。切入點(pointcut):定義了通知應該應用在哪些連接點,通知可以應用到AOP框架支持的任何連接點。引介(introduction):為類添加新方法和屬性。目標對象(target):被通知的對象。既可以是自己編寫的類也可以是第三方類。代理(proxy):將通知應用到目標對象后創建的對象,應用系統的其他部分不用為了支持代理對象而改變。織入(Weaving):將切面應用到目標對象從而創建一個新代理對象的過程。織入發生在目標對象生命周期的多個點上: 編譯期:切面在目標對象編譯時織入。這需要一個特殊的編譯器。 類裝載期:切面在目標對象被載入JVM時織入。這需要一個特殊的類載入器。 運行期:切面在應用系統運行時織入。不需要特殊的編譯器。PS:spring只支持方法連接點,不提供屬性接入點,spring的觀點是屬性攔截破壞了封裝。面向對象的概念是對象自己處理工作,其他對象只能通過方法調用的得到的結果。
Spring的基于AspectJ的AOP的XML開發
第一步引入jar包

第二步創建配置文件
引入AOP的約束<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"     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>第三步創建需增強包和類
創建接口package com.pc.aop.dao;/** * 客戶持久層接口 *  * @author Switch * @data 2016年11月23日 * @version V1.0 */public interface CustomerDao {    /**     * 保存用戶     */    public void save();}創建實現類package com.pc.aop.dao.impl;import com.pc.aop.dao.CustomerDao;/** * 用戶持久層實現類 *  * @author Switch * @data 2016年11月23日 * @version V1.0 */public class CustomerDaoImpl implements CustomerDao {    @Override    public void save() {        System.out.println("保存用戶了。。。");    }}第四步將類交給Spring管理
<bean id="customerDao" class="com.pc.aop.dao.impl.CustomerDaoImpl" />第五步編寫測試
package com.pc.test;import javax.annotation.Resource;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import com.pc.aop.dao.CustomerDao;/** * 面向切面編程測試類 *  * @author Switch * @data 2016年11月23日 * @version V1.0 */// 配置Spring單元測試環境@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class SpringAOPTest {    // 注入依賴    @Resource(name = "customerDao")    private CustomerDao customerDao;    // 測試Spring單元測試集成    @Test    public void testSpring() {        customerDao.save();    }}輸出
保存用戶了。。。PS:這時候沒用使用AOP進行任何增強
第六步編寫切面類和通知
package com.pc.aop.advice;import org.aspectj.lang.ProceedingJoinPoint;/** * 切面類 *  * @author Switch * @data 2016年11月23日 * @version V1.0 */public class MyAspect {    // 通知:校驗權限    public void check() {        System.out.println("校驗權限。。。。。。");    }}第七步 配置切面類
<!-- 配置切面類 --><bean id="myAspect" class="com.pc.aop.advice.MyAspect"/>第八步通過配置實現AOP
<!-- 面向切面配置,基于aspectJ --><aop:config>    <!-- 配置切入點 ,切入點是通過表達式來實現的-->    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>    <!-- 配置切面,切面是由具體的切入點和通知組成的 -->    <aop:aspect ref="myAspect">        <!-- 增強:前置通知 -->        <aop:before method="check" pointcut-ref="pointcut1"/>    </aop:aspect></aop:config>第九步執行測試
package com.pc.test;import javax.annotation.Resource;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import com.pc.aop.dao.CustomerDao;/** * 面向切面編程測試類 *  * @author Switch * @data 2016年11月23日 * @version V1.0 */// 配置Spring單元測試環境@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class SpringAOPTest {    // 注入依賴    @Resource(name = "customerDao")    private CustomerDao customerDao;    // 測試Spring單元測試集成    @Test    public void testSpring() {        customerDao.save();    }}輸出
校驗權限。。。。。。保存用戶了。。。切入點表達式的定義
切入點表達式定義語法
切入點表達式基于execution之類的方法來實現的。在方法內部就可以編寫切入點表達式: 表達式語法:[方法訪問修飾符] 方法返回值 [包名.類名.]方法名(參數) [異常類型]
舉例:
// 所有的public方法public * *(..)// 所有service中的public方法public * com.pc.service.*.*(..)// 所有以find開頭的方法* find*(..)// public修飾符,void返回類型,全路徑匹配public void com.pc.aop.dao.impl.CustomerDaoImpl.save(..)// 任意返回類型,全路徑匹配* com.pc.aop.dao.impl.CustomerDaoImpl.save(..)// 任意返回類型,類名以Dao結尾下的任意方法* com.pc.dao.*Dao.*(..)// 任意返回類型,CustomerDao及其子類下的任意方法* com.pc.dao.CustomerDao+.*(..)// 任意返回類型,com.pc.dao包下的任意方法* com.pc.dao..*.*(..)通知類型

前置通知
前置通知:在目標方法執行之前完成的增強。獲得切入點信息。 PS:接入點信息,其他類型的增強也可以通過這種方法使用。
創建需增強類和方法
public class CustomerDaoImpl implements CustomerDao {    @Override    public void save() {        System.out.println("保存用戶了。。。");    }}創建切面和通知
public class MyAspect {    // 通知:校驗權限    public void check(JoinPoint joinPoint) {        System.out.println("校驗權限。。。。。。");        // 輸出接入點信息        System.out.println(joinPoint.toString());    }}配置切面和通知
<!-- 配置切面類 --><bean id="myAspect" class="com.pc.aop.advice.MyAspect"/><!-- 面向切面配置,基于aspectJ --><aop:config>    <!-- 配置切入點 ,切入點是通過表達式來實現的-->    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>    <!-- 配置切面,切面是由具體的切入點和通知組成的 -->    <aop:aspect ref="myAspect">        <!-- 增強:前置通知 -->        <aop:before method="check" pointcut-ref="pointcut1"/>    </aop:aspect></aop:config>單元測試
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class SpringAOPTest {    // 注入依賴    @Resource(name = "customerDao")    private CustomerDao customerDao;    // 測試前置通知    @Test    public void testBefore() {        customerDao.save();    }}輸出
校驗權限。。。。。。execution(void com.pc.aop.dao.CustomerDao.save())保存用戶了。。。后置通知
后置通知:在目標方法執行之后完成的增強。獲得方法的返回值。
創建需增強類和方法
public class CustomerDaoImpl implements CustomerDao {    @Override    public String delete() {        System.out.println("刪除用戶了。。。");        return "delete";    }}創建切面和通知
public class MyAspect {    // 通知:打印日志    public void printLog(String retVal) {        System.out.println("打印日志。。。。。");        System.out.println("返回值為:" + retVal);    }}配置切面和通知
<!-- 配置切面類 --><bean id="myAspect" class="com.pc.aop.advice.MyAspect"/><!-- 面向切面配置,基于aspectJ --><aop:config>    <!-- 配置切入點 ,切入點是通過表達式來實現的-->    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.*.delete(..))" id="pointcut2"/>    <!-- 配置切面,切面是由具體的切入點和通知組成的 -->    <aop:aspect ref="myAspect">        <!-- 增強:后置通知 -->        <aop:after-returning method="printLog" pointcut-ref="pointcut2" returning="retVal"/>    </aop:aspect></aop:config>單元測試
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class SpringAOPTest {    // 注入依賴    @Resource(name = "customerDao")    private CustomerDao customerDao;    // 測試后置通知    @Test    public void testAfterRunning() {        String delete = customerDao.delete();        System.out.println(delete);    }}輸出
刪除用戶了。。。打印日志。。。。。返回值為:deletedelete環繞通知
環繞通知:在目標方法執行前和執行后完成的增強。阻止目標方法的執行,獲得方法參數。
創建需增強類和方法
public class CustomerDaoImpl implements CustomerDao {    @Override    public void update(Integer id) {        System.out.println("更新用戶了。。。");    }}創建切面和通知
public class MyAspect {    // 通知:計算方法耗時    public void calTime(ProceedingJoinPoint joinPoint) throws Throwable {        System.out.println("方法執行前。。。。。。");        // 打印參數        System.out.println("參數為:" + joinPoint.getArgs()[0]);        // 執行目標方法        joinPoint.proceed();        System.out.println("方法執行后。。。。。。");    }}配置切面和通知
<!-- 配置切面類 --><bean id="myAspect" class="com.pc.aop.advice.MyAspect"/><!-- 面向切面配置,基于aspectJ --><aop:config>    <!-- 配置切入點 ,切入點是通過表達式來實現的-->    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.*.update(..))" id="pointcut3"/>    <!-- 配置切面,切面是由具體的切入點和通知組成的 -->    <aop:aspect ref="myAspect">        <!-- 增強:環繞通知 -->        <aop:around method="calTime" pointcut-ref="pointcut3"/>    </aop:aspect></aop:config>單元測試
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class SpringAOPTest {    // 注入依賴    @Resource(name = "customerDao")    private CustomerDao customerDao;    // 測試環繞通知    @Test    public void testAround() {        customerDao.update(6166);    }}輸出
方法執行前。。。。。。參數為:6166更新用戶了。。。方法執行后。。。。。。異常拋出通知
異常拋出通知:在目標方法執行出現異常的時候完成的增強。獲得異常的信息。
創建需增強類和方法
public class CustomerDaoImpl implements CustomerDao {    @Override    public void find() {        System.out.println("查詢用戶了。。。");        int i = 1 / 0;    }}創建切面和通知
public class MyAspect {    // 通知:異常處理    public void throwHandler(Throwable ex) {        System.out.println("異常處理。。。。。。" + ex.getMessage());    }}配置切面和通知
<!-- 配置切面類 --><bean id="myAspect" class="com.pc.aop.advice.MyAspect"/><!-- 面向切面配置,基于aspectJ --><aop:config>    <!-- 配置切入點 ,切入點是通過表達式來實現的-->    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.*.find())" id="pointcut4"/>    <!-- 配置切面,切面是由具體的切入點和通知組成的 -->    <aop:aspect ref="myAspect">        <!-- 增強:異常通知 -->        <aop:after-throwing method="throwHandler" pointcut-ref="pointcut4" throwing="ex"/>    </aop:aspect></aop:config>單元測試
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class SpringAOPTest {    // 注入依賴    @Resource(name = "customerDao")    private CustomerDao customerDao;    // 測試異常通知    @Test    public void testAfterThrowing() {        customerDao.find();    }}輸出
查詢用戶了。。。異常處理。。。。。。/ by zero最終通知
最終通知:無論目標方法是否出現異常總是執行的增強。
PS:該案例和異常測試案例對同一個target進行增強。
創建需增強類和方法
public class CustomerDaoImpl implements CustomerDao {    @Override    public void find() {        System.out.println("查詢用戶了。。。");        int i = 1 / 0;    }}創建切面和通知
public class MyAspect {    // 通知:關閉資源    public void close() {        System.out.println("關閉資源。。。。。。");    }}配置切面和通知
<!-- 配置切面類 --><bean id="myAspect" class="com.pc.aop.advice.MyAspect"/><!-- 面向切面配置,基于aspectJ --><aop:config>    <!-- 配置切入點 ,切入點是通過表達式來實現的-->    <aop:pointcut expression="execution(* com.pc.aop.dao.impl.*.find())" id="pointcut4"/>    <!-- 配置切面,切面是由具體的切入點和通知組成的 -->    <aop:aspect ref="myAspect">        <!-- 增強:最終通知 -->        <aop:after method="close" pointcut-ref="pointcut4"/>    </aop:aspect></aop:config>單元測試
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class SpringAOPTest {    // 注入依賴    @Resource(name = "customerDao")    private CustomerDao customerDao;    // 測試最終通知    @Test    public void testFinally() {        customerDao.find();    }}輸出
查詢用戶了。。。關閉資源。。。。。。異常處理。。。。。。/ by zeroPS:之后會在《Spring AOP技術(基于AspectJ)的Annotation開發中》中會介紹怎么使用注解進行AOP開發,用注解進行AOP開發相對于XML來說更便捷,也更容易理解。這兩種掌握一種就可以了,但是如果要對AOP進行集中管理,最好還是使用XML方式。
Spring AOP小項目
GitHub:Spring AOP小項目 GitHub:MyStore-netease