【spring】的AOP詳解

1.什麼是AOP

在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP(面向對象編程)的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。

  • AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性代碼
  • 經典應用:事務管理、性能監視、安全檢查、緩存 、日誌等
  • Spring AOP使用純Java實現,不需要專門的編譯過程和類加載器,在運行期通過代理方式向目標類織入增強代碼
  • AspectJ是一個基於Java語言的AOP框架,Spring2.0開始,Spring AOP引入對Aspect的支持,AspectJ擴展了Java語言,提供了一個專門的編譯器,在編譯時提供橫向代碼的織入

2.AOP實現原理

  • aop底層將採用代理機制進行實現。
  • 接口 + 實現類 :spring採用 jdk 的動態代理Proxy。
  • 實現類:spring 採用 cglib字節碼增強。

3.AOP術語

target:目標類,需要被代理的類。例如:UserService
Joinpoint(連接點):所謂連接點是指那些可能被攔截到的方法。例如:所有的方法
PointCut 切入點:已經被增強的連接點。例如:addUser()
advice 通知/增強,增強代碼。例如:after、before
Weaving(織入):是指把增強advice應用到目標對象target來創建新的代理對象proxy的過程.
proxy 代理類
Aspect(切面): 是切入點pointcut和通知advice的結合

  • 一個線是一個特殊的面。

  • 一個切入點和一個通知,組成成一個特殊的面。

這裏寫圖片描述

4.手動方式

  • 4.1 JDK動態代理
    JDK動態代理對“裝飾者”設計模式簡化。使用前提:必須有接口
目標類:接口 + 實現類
public interface UserService {  
    public void addUser();
    public void updateUser();
    public void deleteUser();

}

切面類:用於通知 MyAspect
public class MyAspect {

    public void before(){
        System.out.println("雞首");
    }

    public void after(){
        System.out.println("牛後");
    }

}

工廠類:編寫工廠生成代理
public class MyBeanFactory {

    public static UserService createService(){
        //1 目標類
        final UserService userService = new UserServiceImpl();
        //2 切面類
        final MyAspect myAspect = new MyAspect();
        /*3 代理類:將目標類(切入點)和 切面類(通知) 結合 --> 切面
         *  Proxy.newProxyInstance
         *      參數1:loader ,類加載器,動態代理類 運行時創建,任何類都需要類加載器將其加載到內存。
         *          一般情況:當前類.class.getClassLoader();
         *                  目標類實例.getClass().get...
         *      參數2:Class[] interfaces 代理類需要實現的所有接口
         *          方式1:目標類實例.getClass().getInterfaces()  ;注意:只能獲得自己接口,不能獲得父元素接口
         *          方式2:new Class[]{UserService.class}   
         *          例如:jdbc 驅動  --> DriverManager  獲得接口 Connection
         *      參數3:InvocationHandler  處理類,接口,必須進行實現類,一般採用匿名內部
         *          提供 invoke 方法,代理類的每一個方法執行時,都將調用一次invoke
         *              參數31:Object proxy :代理對象
         *              參數32:Method method : 代理對象當前執行的方法的描述對象(反射)
         *                  執行方法名:method.getName()
         *                  執行方法:method.invoke(對象,實際參數)
         *              參數33:Object[] args :方法實際參數
         * 
         */
        UserService proxService = (UserService)Proxy.newProxyInstance(
                                MyBeanFactory.class.getClassLoader(), 
                                userService.getClass().getInterfaces(), 
                                new InvocationHandler() {

                                    @Override
                                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                                        //前執行
                                        myAspect.before();

                                        //執行目標類的方法
                                        Object obj = method.invoke(userService, args);

                                        //後執行
                                        myAspect.after();

                                        return obj;
                                    }
                                });

        return proxService;
    }

}

測試
@Test
public void demo01(){
    UserService userService = MyBeanFactory.createService();
    userService.addUser();
    userService.updateUser();
    userService.deleteUser();
}
  • 4.2 CGLIB字節碼增強
    沒有接口,只有實現類。採用字節碼增強框架 cglib,在運行時創建目標類的子類,從而對目標類進行增強。導入jar包:spring-core..jar 已經整合以下兩個內容
    核心:hibernate-distribution-3.6.10.Final\lib\bytecode\cglib\cglib-2.2.jar
    依賴:struts-2.3.15.3\apps\struts2-blank\WEB-INF\lib\asm-3.3.jar
工廠類
public class MyBeanFactory {

    public static UserServiceImpl createService(){
        //1 目標類
        final UserServiceImpl userService = new UserServiceImpl();
        //2 切面類
        final MyAspect myAspect = new MyAspect();
        //3 代理類 ,採用cglib,底層創建目標類的子類
        //3.1 核心類
        Enhancer enhancer = new Enhancer();
        //3.2 確定父類
        enhancer.setSuperclass(userService.getClass());
        /*3.3 設置回調函數 , MethodInterceptor接口 等效 jdk InvocationHandler接口
         *  intercept() 等效 jdk  invoke()
         *      參數1、參數2、參數3:以invoke一樣
         *      參數4:methodProxy 方法的代理
         *      
         * 
         */
        enhancer.setCallback(new MethodInterceptor(){

            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

                //前
                myAspect.before();

                //執行目標類的方法
                Object obj = method.invoke(userService, args);
                // * 執行代理類的父類 ,執行目標類 (目標類和代理類 父子關係)
                methodProxy.invokeSuper(proxy, args);

                //後
                myAspect.after();

                return obj;
            }
        });
        //3.4 創建代理
        UserServiceImpl proxService = (UserServiceImpl) enhancer.create();

        return proxService;
    }

}

5.AOP聯盟通知類型

  • AOP聯盟爲通知Advice定義了org.aopalliance.aop.Advice
  • Spring按照通知Advice在目標類方法的連接點位置,可以分爲5類
    • 前置通知 org.springframework.aop.MethodBeforeAdvice
    • 在目標方法執行前實施增強
    • 後置通知 org.springframework.aop.AfterReturningAdvice
    • 在目標方法執行後實施增強
    • 環繞通知 org.aopalliance.intercept.MethodInterceptor
    • 在目標方法執行前後實施增強
    • 異常拋出通知 org.springframework.aop.ThrowsAdvice
    • 在方法拋出異常後實施增強
    • 引介通知 org.springframework.aop.IntroductionInterceptor
    • 在目標類中添加一些新的方法和屬性
環繞通知,必須手動執行目標方法
try{
   //前置通知
   //執行目標方法
   //後置通知
} catch(){
   //拋出異常通知
}

6.spring編寫代理:半自動
讓spring 創建代理對象,從spring容器中手動的獲取代理對象。

目標類
public interface UserService {

    public void addUser();
    public void updateUser();
    public void deleteUser();

}

切面類
/**
 * 切面類中確定通知,需要實現不同接口,接口就是規範,從而就確定方法名稱。
 * 採用“環繞通知” MethodInterceptor
 *
 */
public class MyAspect implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {

        System.out.println("前3");

        //手動執行目標方法
        Object obj = mi.proceed();

        System.out.println("後3");
        return obj;
    }
}

spring配置
<!-- 1 創建目標類 -->
<bean id="userServiceId" class="com.spring.demo.UserServiceImpl"></bean>
<!-- 2 創建切面類 -->
<bean id="myAspectId" class="com.spring.demo.MyAspect"></bean>

<!-- 3 創建代理類 
    * 使用工廠bean FactoryBean ,底層調用 getObject() 返回特殊bean
    * ProxyFactoryBean 用於創建代理工廠bean,生成特殊代理對象
        interfaces : 確定接口
            通過<array>可以設置多個值
            只有一個值時,value=""
        target : 確定目標類
        interceptorNames : 通知 切面類的名稱,類型String[],如果設置一個值 value=""
        optimize :強制使用cglib
            <property name="optimize" value="true"></property>  
-->
<bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="interfaces" value="com.spring.demo.UserService"></property>
    <property name="target" ref="userServiceId"></property>
    <property name="interceptorNames" value="myAspectId"></property>
</bean>

底層機制:如果目標類有接口,採用jdk動態代理,如果沒有接口,採用cglib 字節碼增強,如果聲明 optimize = true ,無論是否有接口,都採用cglib。

@Test
public void demo01(){
    String xmlPath = "com/spring/demo/beans.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);

    //獲得代理類
    UserService userService = (UserService) applicationContext.getBean("proxyServiceId");
    userService.addUser();
    userService.updateUser();
    userService.deleteUser();
}

7.spring aop編程:全自動
從spring容器獲得目標類,如果配置aop,spring將自動生成代理。要確定目標類,aspectj 切入點表達式,導入jar包spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE。

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"
       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">
    <!-- 1 創建目標類 -->
    <bean id="userServiceId" class="com.spring.demo.UserServiceImpl"></bean>
    <!-- 2 創建切面類(通知) -->
    <bean id="myAspectId" class="com.spring.demo.MyAspect"></bean>
    <!-- 3 aop編程 
        3.1 導入命名空間
        3.2 使用 <aop:config>進行配置
                proxy-target-class="true" 聲明時使用cglib代理
            <aop:pointcut> 切入點 ,從目標對象獲得具體方法
            <aop:advisor> 特殊的切面,只有一個通知 和 一個切入點
                advice-ref 通知引用
                pointcut-ref 切入點引用
        3.3 切入點表達式
            execution(* com.spring.demo.*.*(..))
            選擇方法 返回值任意   包     類名任意   方法名任意   參數任意
    -->
    <aop:config proxy-target-class="true">
        <aop:pointcut expression="execution(* com.spring.demo.*.*(..))" id="myPointCut"/>
        <aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut"/>
    </aop:config>
</beans>

測試
@Test
public void demo01(){
    String xmlPath = "com/spring/demo/beans.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);

    //獲得代理類
    UserService userService = (UserService) applicationContext.getBean("proxyServiceId");
    userService.addUser();
    userService.updateUser();
    userService.deleteUser();
}
發佈了64 篇原創文章 · 獲贊 10 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章