Spring AOP之基於代理類的AOP實現和AspectJ開發

一,什麼是AOP

AOP全稱是Aspect-Oriented Programming,即面向切面編程。AOP採用橫向抽取的機制,將分散在各個方法中的重複代碼提取出來,然後在程序編譯或運行時,在將這些提取出來的代碼應用到需要執行的地方。

二,AOP術語

  • Aspect(切面):通常指封裝着用於橫向插入系統功能的類
  • JointPoint(連接點):在程序執行過程中的某個階段點。在Spring AOP中,連接點是指方法的調用
  • Pointcut(切入點):是指切面與程序流程的交叉點,即那些需要處理的連接點
  • Advice(通知/增強處理):AOP框架在特定的切入點執行的增強處理,可以理解爲切面類中的方法
  • Target Object(目標對象):是指所有被通知的對象
  • Proxy(代理):將通知應用到目標對象之後,被動態創建的對象
  • Weaving(織入):將切面代碼插入到目標對象上,從而生成代理對象的過程

三,基於代理類的AOP實現

其實現是通過ProxyFactoryBean將切面類織入目標對象。

  1. 怎麼定義切面類
    切面類需要實現一些特定的接口,並在其中定義好通知。
    可以實現的接口有如下:
    • org.aopalliance.intercept.MethodInterceptor(環繞通知)
      在目標方法執行前後實施增強,可以應用於日誌、事務管理等功能
    • org.springframework.aop.MethodBeforeAdvice(前置通知)
      在目標方法執行前實施增強,可以應用於權限管理等功能
    • org.springframework.aop.AfterReturningAdvice(後置通知)
      在目標方法執行後實施增強,可以應用於關閉流、上傳文件、刪除臨時文件等功能
    • org.springframework.aop.ThrowsAdvice(異常通知)
      在方法拋出異常後實施增強,可以應用於處理異常記錄日誌等功能
    • org.springframework.aop.IntroductionInterceptor(引介通知)
      在目標類中添加新的方法和屬性,可以應用於修改老版本
      下面是編寫名爲MyAspect的環繞通知的切面類的實現代碼
package com.itheima.factorybean;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAspect implements MethodInterceptor{
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        check_Permissions();
        Object obj = mi.proceed();
        log();
        return obj;
    }

    public void check_Permissions() {
        System.out.println("模擬檢查權限。。。");
    }
    public void log() {
        System.out.println("模擬記錄日誌。。。");
    }
}
  1. ProxyFactoryBean實現織入的過程
    織入是在xml文件中編寫相關代碼實現的。
    下面的xml文件中定義了UserDaoImpl的Bean,MyAspect切面類的Bean和一個ProxyFactoryBean的Bean。ProxyFactoryBean需要設置proxyInterfaces屬性來指定目標對象實現的接口,如果有多個接口,則用<list><value></value>…</list>的格式進行賦值。target屬性用來指定目標對象,interceptorNames指定需要織入的切面類,屬性proxyTargetClass屬性指定是否對類代理而不是接口,設置爲true時,使用CGLIB代理。
<?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">
    <bean id="userDao" class="com.itheima.jdk.UserDaoImpl"/>
    <bean id="myAspect" class="com.itheima.factorybean.MyAspect"/>
    <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces" value="com.itheima.jdk.UserDao"/>
        <property name="target" ref="userDao"/>
        <property name="interceptorNames" value="myAspect"/>
        <property name="proxyTargetClass" value="true"/>
    </bean>
</beans>

以下是其他相關代碼

package com.itheima.jdk;

public interface UserDao {
    public void addUser();
    public void deleteUser();
}
package com.itheima.jdk;

import java.io.IOException;

public class UserDaoImpl implements UserDao{
    public void addUser(){
        System.out.println("添加用戶");
    }
    public void deleteUser() {
        System.out.println("刪除用戶");
    }
}
package com.itheima.jdk;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ProxyFactoryBeanTest {
    public static void main(String[] args) {
        String xmlPath = "com/itheima/factorybean/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
        userDao.addUser();
    }
}

四,AspectJ實現AOP

基於XML的聲明式AspectJ

在beans標籤下包含的aop:config標籤中進行配置,aop:config標籤可以包含子元素或者屬性,其子元素包含aop:pointcut,aop:advisor,aop:aspect。aop:aspect標籤下可以配置切入點和通知。

  1. 配置切面
    aop:aspect扁鵲用來配置切面,其id屬性用於定義該切面的唯一標識,ref用於引用普通的Spring Bean。
  2. 配置切入點
    配置切入點使用aop:pointcut標籤。其id屬性用來指定切入面的唯一標識。expression屬性用來指定切入點關聯的切入點表達式。
    表達式的基本格式如下
    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
    上述格式說明:
    • modifiers-pattern:表示定義的目標方法的訪問修飾符,如public、private等
    • ret-type-pattern:表示定義的目標方法的返回值類型,如void、String等
    • declaring-type-pattern:表示定義的目標方法的類路徑,如com.itheima.jdk.UserDaoImpl
    • name-pattern:表示具體需要被代理的目標方法,如add()方法
    • param-pattern:表示需要被代理的目標方法包含的參數
    • throws-pattern:表示需要被代理的目標方法拋出的異常類型
      其中帶有?的是表示可配置項,其他爲不可配置項。
  3. 配置通知
    配置通知的常見標籤有前置通知aop:before,後置通知aop:after-returning,環繞通知aop:around,異常通知aop:after-throwing,最終通知aop:after。
    這些標籤不支持使用子元素,在使用是可以指定一些屬性。
    • pointcut:只當一個切入點表達式
    • pointcut-ref:指定一個已經存在的切入點名稱
    • method:該屬性指定一個方法名,指定將切面Bean中的該方法轉換爲增強處理
    • throwing:該屬性只對after-throwing元素有效,用於指定一個形參名,異常通知方法可以通過該形參訪問目標方法所拋出的異常
    • returning:該屬性支隊after-returning元素有效,用於指定一個形參名,後置通知方法可以通過該形參訪問目標方法的返回值

演示代碼如下

package com.itheima.jdk;

public interface UserDao {
    public void addUser();
    public void deleteUser();
}
package com.itheima.jdk;

public class UserDaoImpl implements UserDao{
    public void addUser(){
        System.out.println("添加用戶");
    }
    public void deleteUser() {
        System.out.println("刪除用戶");
    }
}
package com.itheima.aspectj.xml;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class MysAspect {
    //前置通知
    public void myBefore(JoinPoint joinpoint){
        System.out.print("前置通知:模擬執行權限檢查。。。,");
        System.out.print("目標類是:" + joinpoint.getTarget());
        System.out.println(",被植入增強的處理的目標方法爲:" +
                joinpoint.getSignature().getName());
    }

    //後置通知
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("後置通知:模擬記錄日誌。。。,");
        System.out.println("被植入增強處理的目標方法爲:" +
                joinPoint.getSignature().getName());
    }

    //環繞通知
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //開始
        System.out.println("環繞開始:執行目標方法之前,模擬開啓事務。。。");
        //執行當前目標方法
        Object obj = proceedingJoinPoint.proceed();
        //結束
        System.out.println("環繞結束:執行目標方法之後,模擬關閉事務。。。");
        return obj;
    }

    //異常通知
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("異常通知:" + "出錯了" + e.getMessage());
    }

    //最終通知
    public void myAfter() {
        System.out.println("最終通知:模擬方法結束後的釋放資源。。。");
    }
}
<?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">
    <bean id="userDao" class="com.itheima.jdk.UserDaoImpl"/>
    <bean id="mysAspect" class="com.itheima.aspectj.xml.MysAspect"/>
    <aop:config>
        <aop:aspect ref="mysAspect">
            <aop:pointcut id="myPointCut" expression="execution(* com.itheima.jdk.*.*(..))"/>
            <aop:before method="myBefore" pointcut-ref="myPointCut"/>
            <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut"/>
            <aop:around method="myAround" pointcut-ref="myPointCut"/>
            <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
            <aop:after method="myAfter" pointcut-ref="myPointCut"/>
        </aop:aspect>
    </aop:config>
</beans>
package com.itheima.aspectj.xml;

import com.itheima.jdk.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestXmlAspectj {
    public static void main(String[] args) {
        String xmlPath = "com/itheima/aspectj/xml/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        userDao.addUser();
    }
}

基於註解的聲明式AspectJ

用註解來代替xml配置
* @Aspect:用於定義一個切面
* @Pointcut:用於定義切入點表達式。在使用時還需要定義一個包含名字和任意參數的方法簽名來表示切入點名稱。實際上,這個方法簽名就是一個返回值爲void,且方法體爲空的普通的方法。
* @Before:用於定義前置通知,相當於BeforeAdivice。在使用時,需要指定一個value值,指定一個切入點表達式
* @AfterReturning:用於定義後置通知,相當於AfterReturningAdivice。使用時指定pointcut或value和returning屬性,pointcut或value這兩個屬性用於指定切入表達式,returning屬性用於表示Advice方法中可定義與此同名的形參,該形參用於訪問目標方法的返回值
* @Around:用於定義環繞通知,相當於MethodInterceptor,使用時設置value屬性,指定切入點
* AfterThrowing:用於定義異常通知來處理,相當於ThrowAdvice。使用時可指定ponitcut或value和throwing屬性,pointcut或value用於指定切入點表達式,throwing用於指定一個形參名來表示Advice方法中可定義與此同名的形參
* @After:用於定義最終通知,不管是否異常,該通知都會執行。使用時可指定value值,用於指定切入點
* DeclareParents:用於定義引介通知,相當於IntroductionInterceptor
下面時演示代碼

package com.itheima.jdk;

public interface UserDao {
    public void addUser();
    public void deleteUser();
}
package com.itheima.jdk;

import org.springframework.stereotype.Repository;

@Repository("userDao")
public class UserDaoImpl implements UserDao{
    public void addUser(){
        System.out.println("添加用戶");
    }
    public void deleteUser() {
        System.out.println("刪除用戶");
    }
}
package com.itheima.aspectj.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MysAspect {
    @Pointcut("execution(* com.itheima.jdk.*.*(..))")
    private void myPointCut(){}
    //前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinpoint){
        System.out.print("前置通知:模擬執行權限檢查。。。,");
        System.out.print("目標類是:" + joinpoint.getTarget());
        System.out.println(",被植入增強的處理的目標方法爲:" +
                joinpoint.getSignature().getName());
    }

    //後置通知
    @AfterReturning("myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("後置通知:模擬記錄日誌。。。,");
        System.out.println("被植入增強處理的目標方法爲:" +
                joinPoint.getSignature().getName());
    }

    //環繞通知
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //開始
        System.out.println("環繞開始:執行目標方法之前,模擬開啓事務。。。");
        //執行當前目標方法
        Object obj = proceedingJoinPoint.proceed();
        //結束
        System.out.println("環繞結束:執行目標方法之後,模擬關閉事務。。。");
        return obj;
    }

    //異常通知
    @AfterThrowing(value = "myPointCut()", throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("異常通知:" + "出錯了" + e.getMessage());
    }

    //最終通知
    @After("myPointCut()")
    public void myAfter() {
        System.out.println("最終通知:模擬方法結束後的釋放資源。。。");
    }
}
<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.itheima.jdk"/>
    <context:component-scan base-package="com.itheima.aspectj.annotation"/>
    <aop:aspectj-autoproxy/>
</beans>
package com.itheima.aspectj.annotation;

import com.itheima.jdk.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAnnotation {
    public static void main(String[] args) {
        String xmlPath = "com/itheima/aspectj/annotation/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        userDao.addUser();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章