Spring 面向切面編程(AOP)

基於 XML 配置文件的 AOP

一、創建切入點類

public class PointCutBean {

    public void sayHello() {
        System.out.println("Hello World!");
    }
}

二、創建通知類

public class AdviceBean {

    public void writeLine() {
        System.out.println("------------------------------");
    }
}

三、創建 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">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的類型,並且建立通知方法和切入點方法的關聯 -->
            <aop:before method="writeLine" pointcut="execution(* chu.yi.bo.PointCutBean.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

四、測試

public void testAOP() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Application.xml");
    PointCutBean pointCutBean = (PointCutBean)applicationContext.getBean("pointCutBean");
    pointCutBean.sayHello();
}

五、切入點表達式

      切入點表達式用來進行切入點(方法)的匹配,語法結構是:execution([修飾符] 返回值類型 包名.類名.方法名(參數))。修飾符是非必選項,返回值類型、包名的任意一級、類名、方法名、參數,都可以用通配符 * 來代替。包名、參數名也可以用通配符 . . 來代替,例子如下:

  • 無省略模式
    execution(public void chu.yi.bo.PointCutBean.sayHello())
  • 省略修飾符
    execution(void chu.yi.bo.PointCutBean.sayHello())
  • 使用 * 號代替返回值,表示任意返回類型
    execution(*  chu.yi.bo.PointCutBean.sayHello())
  • 使用 * 號替代任意一級包
    execution(*   *.*.*.PointCutBean.sayHello())
  • 使用 .. 表示當前包以及子包
    execution(*   chu..PointCutBean.sayHello())
  • 使用 * 號代替類名,表示任意類
    execution(*   chu..*.sayHello())
  • 使用 * 號代替方法名,表示任意方法
    execution(*   chu..*.*())
  • 方法有參數時,可以使用 * 替代任意類型參數
    execution(*   chu..*.*(*))
  • 無論方法有無參數都可以使用 .. 代替任意類型參數
    execution(*   chu..*.*(..))
  • 全使用通配符
    execution(*   *..*.*(..))

配置 AOP 相關標籤

標籤 說明 屬性
aop:config 用於聲明開始 AOP 的配置
aop:aspect 用於配置切面 id:給切面提供唯一標識
ref:引用配置好的通知 bean 的 id
aop:pointcut 指定對哪些類的哪些方法進行增強,配置切入點表達式 expression:用於定義切入點表達式
id:給切入點表達式提供唯一標識
aop:before 配置前置通知。指定增強的方法在切入點方法之前執行 method:用於指定通知類中的增強方法名稱
ponitcut-ref:用於指定切入點的表達式的引用
poinitcut:用於指定切入點表達式
aop:after-returning 配置後置通知,切入點方法正常執行之後。它和異常通知只能有一個執行 同上
aop:after-throwing 配置異常通知,切入點方法執行產生異常後執行。它和後置通知只能執行一個 同上
aop:after 配置最終通知,無論切入點方法執行時是否有異常,它都會在其後面執行 同上
aop:around 配置環繞通知 同上

一、aop:pointcut 標籤使用

<?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="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入點 -->
        <aop:pointcut id="pt" expression="execution(* chu.yi.bo.PointCutBean.*(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的類型,並且建立通知方法和切入點方法的關聯 -->
            <aop:before method="writeLine" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>
</beans>

      aop:pointcut 標籤用於配置切入點,該標籤寫在 aop:aspect 標籤內部只能在當前切面使用,寫在 aop:aspect 標籤外部可以在所有切面使用。

通知類型

一、前置通知

1、切入點類

public class PointCutBean {

    public void beforePointCut() {
        System.out.println("beforePointCut");
    }
}

2、通知類

public class AdviceBean {

    public void beforeAdvice() {
        System.out.println("beforeAdvice");
    }
}

3、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">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入點 -->
        <aop:pointcut id="beforePt" expression="execution(* chu.yi.bo.PointCutBean.beforePointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的類型,並且建立通知方法和切入點方法的關聯 -->
            <aop:before method="beforeAdvice" pointcut-ref="beforePt"/>
        </aop:aspect>
    </aop:config>
</beans>

二、後置通知

1、切入點類

public class PointCutBean {

    public void afterReturningPointCut() {
        System.out.println("afterReturningPointCut");
    }
}

2、通知類

public class AdviceBean {

    public void afterReturningAdvice() {
        System.out.println("afterReturningAdvice");
    }
}

3、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">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入點 -->
        <aop:pointcut id="afterReturningPt" expression="execution(* chu.yi.bo.PointCutBean.afterReturningPointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的類型,並且建立通知方法和切入點方法的關聯 -->
            <aop:after-returning method="afterReturningAdvice" pointcut-ref="afterReturningPt"/>
        </aop:aspect>
    </aop:config>
</beans>

三、異常通知

1、切入點類

public class PointCutBean {

    public void afterThrowingPointCut() {
        System.out.println("afterThrowingPointCut");
        throw new RuntimeException();
    }
}

2、通知類

public class AdviceBean {

    public void afterThrowingAdvice() {
        System.out.println("afterThrowingAdvice");
    }
}

3、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">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入點 -->
        <aop:pointcut id="afterThrowingPt" expression="execution(* chu.yi.bo.PointCutBean.afterThrowingPointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的類型,並且建立通知方法和切入點方法的關聯 -->
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="afterThrowingPt"/>
        </aop:aspect>
    </aop:config>
</beans>

四、最終通知

1、切入點類

public class PointCutBean {

    public void afterPointCut() {
        System.out.println("afterPointCut");
    }
}

2、通知類

public class AdviceBean {

    public void afterAdvice() {
        System.out.println("afterAdvice");
    }
}

3、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">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入點 -->
        <aop:pointcut id="afterPt" expression="execution(* chu.yi.bo.PointCutBean.afterPointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的類型,並且建立通知方法和切入點方法的關聯 -->
            <aop:after method="afterAdvice" pointcut-ref="afterPt"/>
        </aop:aspect>
    </aop:config>
</beans>

五、環繞通知

1、切入點類

public class PointCutBean {

    public void aroundPointCut() {
        System.out.println("aroundPointCut");
    }
}

2、通知類

public class AdviceBean {

    public Object aroundAdvice(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try {
            Object[] args = pjp.getArgs();// 得到方法執行所需的參數
            System.out.println("beforeAdvice 前置");
            rtValue = pjp.proceed(args);// 明確調用業務層方法(切入點方法)
            System.out.println("afterReturningAdvice 後置");
            return rtValue;
        } catch (Throwable t) {
            System.out.println("afterThrowingAdvice 異常");
            throw new RuntimeException(t);
        } finally {
            System.out.println("afterAdvice 最終");
        }
    }
}

      Spring 框架提供了 ProceedingJoinPoint 接口,程序運行時,Spring 框架會創建該接口的實例,並作爲參數傳遞給通知方法。調用該實例的 proceed() 方法可以執行切入點方法,調用該實例的 getArgs() 方法可以獲取切入點方法的參數列表。

3、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">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入點 -->
        <aop:pointcut id="aroundPt" expression="execution(* chu.yi.bo.PointCutBean.aroundPointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的類型,並且建立通知方法和切入點方法的關聯 -->
            <aop:around method="aroundAdvice" pointcut-ref="aroundPt"/>
        </aop:aspect>
    </aop:config>
</beans>

AOP 相關術語

術語 說明
Joinpoint(連接點) 連接點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因爲 spring 只支持方法類型的連接點。
Pointcut(切入點) 所謂切入點是指要對哪些 Joinpoint 進行攔截的定義
Advice(通知/ 增強) 通知是指攔截到 Joinpoint 之後所要做的事情就是通知。通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知。
Target(目標對象) 代理的目標對象
Weaving(織入) 把增強應用到目標對象來創建新的代理對象的過程
Proxy (代理) 一個類被 AOP 織入增強後,就產生一個結果代理類
Aspect(切面) 是切入點和通知的結合

基於註解實現 AOP

一、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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置 spring 創建容器時要掃描的包 -->
    <context:component-scan base-package="chu.yi.bo"/>

    <!-- 配置 spring 開啓註解 AOP 的支持 -->
    <aop:aspectj-autoproxy/>
</beans>

二、前置通知

1、切入點類

@Component("pointCutBean")
public class PointCutBean {

    public void beforePointCut() {
        System.out.println("beforePointCut");
    }
}

2、通知類

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @Before("execution(* chu.yi.bo.PointCutBean.beforePointCut(..))")
    public void beforeAdvice() {
        System.out.println("beforeAdvice");
    }
}

三、後置通知

1、切入點類

@Component("pointCutBean")
public class PointCutBean {

    public void afterReturningPointCut() {
        System.out.println("afterReturningPointCut");
    }
}

2、通知類

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @AfterReturning("execution(* chu.yi.bo.PointCutBean.afterReturningPointCut(..))")
    public void afterReturningAdvice() {
        System.out.println("afterReturningAdvice");
    }
}

四、異常通知

1、切入點類

@Component("pointCutBean")
public class PointCutBean {

    public void afterThrowingPointCut() {
        System.out.println("afterThrowingPointCut");
        throw new RuntimeException();
    }
}

2、通知類

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @AfterThrowing("execution(* chu.yi.bo.PointCutBean.afterThrowingPointCut(..))")
    public void afterThrowingAdvice() {
        System.out.println("afterThrowingAdvice");
    }
}

五、最終通知

1、切入點類

@Component("pointCutBean")
public class PointCutBean {

    public void afterPointCut() {
        System.out.println("afterPointCut");
    }
}

2、通知類

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @After("execution(* chu.yi.bo.PointCutBean.afterPointCut(..))")
    public void afterAdvice() {
        System.out.println("afterAdvice");
    }
}

六、環繞通知

1、切入點類

@Component("pointCutBean")
public class PointCutBean {

    public void aroundPointCut() {
        System.out.println("aroundPointCut");
    }
}

2、通知類

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @Around("execution(* chu.yi.bo.PointCutBean.aroundPointCut(..))")
    public Object aroundAdvice(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();// 得到方法執行所需的參數

            System.out.println("beforeAdvice 前置");

            rtValue = pjp.proceed(args);// 明確調用業務層方法(切入點方法)

            System.out.println("afterReturningAdvice 後置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("afterThrowingAdvice 異常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("afterAdvice 最終");
        }
    }
}

七、@Pointcut 註解配置切入點

1、切入點類

@Component("pointCutBean")
public class PointCutBean {

    public void beforePointCut() {
        System.out.println("beforePointCut");
    }
}

2、通知類

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @Pointcut("execution(* chu.yi.bo.PointCutBean.beforePointCut(..))")
    private void pt(){}

    @Before("pt()")
    public void beforeAdvice() {
        System.out.println("beforeAdvice");
    }
}

動態代理

      Spring 面向切面編程,底層的實現原理是動態代理。

一、接口

public interface HelloInterface {
    void sayHello();
}

二、被代理類

public class Hello implements HelloInterface {

    public void sayHello() {
        System.out.println("Hello World!");
    }
}

三、Java 官方動態代理

public void proxy() {
    final HelloInterface hello = new Hello();

    HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance(
            hello.getClass().getClassLoader(),
            hello.getClass().getInterfaces(),
            new InvocationHandler() {

                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("Before invoke "  + method.getName());
                    Object rtValue = method.invoke(hello, args);
                    System.out.println("After invoke " + method.getName());
                    return rtValue;
                }
            }
    );
    proxyHello.sayHello();
}

三、使用 CGLib 實現動態代理

public void proxy() {
    final HelloInterface hello = new Hello();

    HelloInterface proxyHello = (HelloInterface) Enhancer.create(hello.getClass(),
            new MethodInterceptor() {

                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    System.out.println("Before invoke "  + method.getName());
                    Object rtValue = method.invoke(hello, objects);
                    System.out.println("After invoke " + method.getName());
                    return rtValue;
                }
            });
    proxyHello.sayHello();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章