天天看點

AOP建立切面

AOP,就是面向切面程式設計。

什麼是切面呢?增強+切點就是切面。需要向切面裡注入一個增強

前面說了增強,這裡我們說一說切點,切點就是特定類的特定方法。

Pointcut = ClassFilter + MethodMatcher.

Advisor = Pointcut + Advice

三種切面類型:

  1. 一般切面
  2. 切點切面
  3. 引介切面

Advisor:一般切面,隻包含增強,一般不會直接使用

PointcutAdvisor.切點切面,就是比較通用的Advice + Pointcut.

IntroductionAdvisor:引介切面。

切面的實作類

  1. 靜态普通方法名比對切面StaticMethodMatcherPointcutAdvisor.

    比對所有的目标類

package com.smart.advisor;

import java.lang.reflect.Method;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {

    public boolean matches(Method method, Class clazz) {
        return "greetTo".equals(method.getName());
    }   
    public ClassFilter getClassFilter(){
        return new ClassFilter(){
            public boolean matches(Class clazz){
                return Waiter.class.isAssignableFrom(clazz);
            }
        };

    }
}
           

隻需要實作matches方法,比對所有的類。

覆寫getClassFilter().讓他僅比對waiter類極其子類。

我們還需要一個增強

package com.smart.advisor;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class GreetingBeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object obj) throws Throwable {
        String clientName = (String)args[];
        System.out.println(obj.getClass().getName()+"."+method.getName());
        System.out.println("How are you!Mr."+clientName+".");
    }
}
           

spring配置來定義切面

<!-- 普通方法名比對切面 -->
    <bean id="waiterTarget" class="com.smart.advisor.Waiter" />
    <bean id="sellerTarget" class="com.smart.advisor.Seller" />

    <bean id="greetingAdvice"
    class="com.smart.advisor.GreetingBeforeAdvice" />
    <!--切面中注入增強-->
    <bean id="greetingAdvisor" 
    class="com.smart.advisor.GreetingAdvisor"
    p:advice-ref="greetingAdvice" />
    <!--公共配置資訊-->
    <bean id="parent" abstract="true"
        class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true" />
    <!--兩個代理Bean通過parent放入代理工廠中-->
    <bean id="waiter" parent="parent" p:target-ref="waiterTarget" />
    <bean id="seller" parent="parent" p:target-ref="sellerTarget" />
           

當有多個類似的方法,需要配置切面的時候,就應該使用正規表達式方法比對切面。

RegexpMethodPointcutAdvisor

<!-- 正規表達式方法名比對切面 -->
    <bean id="regexpAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
        p:advice-ref="greetingAdvice"> <!--增強注入-->
        <property name="patterns"> <!--比對多個正則方法-->
            <list>
                <value>.*greet.*</value>
            </list>
        </property>
    </bean>
    <bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="regexpAdvisor"  <!--表明切面-->
        p:target-ref="waiterTarget" <!--表明目标類-->
        p:proxyTargetClass="true" />
           

動态切面

應用場景:比如服務員打招呼,隻有當顧客是劉洋的時候,她才啟用增強。

這就是動态界面,和方法的入參有關系。的切面

通過繼承DynamicMethodMathcherPointcut類來實作。這個隻是切點類。

通過配置,切點類+增強類 就有了切面。

package com.smart.advisor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.springframework.aop.support.DynamicMethodMatcherPointcut;

public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut {
    private static List<String> specialClientList = new ArrayList<String>();
    static {
        specialClientList.add("John");
        specialClientList.add("Tom");
    }
//  public ClassFilter getClassFilter() {
//      return new ClassFilter() {
//          public boolean matches(Class clazz) {
//              System.out.println("調用getClassFilter()對"+clazz.getName()+"做靜态檢查.");
//              return Waiter.class.isAssignableFrom(clazz);
//          }
//      };
//  }
//  public boolean matches(Method method, Class clazz) {
//      System.out.println("調用matches(method,clazz)對"+clazz.getName()+"."+method.getName()+"做靜态檢查.");
//      return "greetTo".equals(method.getName());
//  }
    public boolean matches(Method method, Class clazz, Object[] args) {
        System.out.println("調用matches(method,clazz)對"+clazz.getName()+"."+method.getName()+"做動态檢查.");
        String clientName = (String) args[];
        return specialClientList.contains(clientName);
    }

}
    <!-- 動态切面 -->
    <bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut">
            <bean class="com.smart.advisor.GreetingDynamicPointcut" />
        </property>
        <property name="advice">
            <bean class="com.smart.advisor.GreetingBeforeAdvice" />
        </property>
    </bean>
        <bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="dynamicAdvisor" p:target-ref="waiterTarget"
        p:proxyTargetClass="true" />
           

流程切面:

前面的動态切面和入參有關,流程切面和調用者有關系。比如:隻有通過service調用的greetto,和serveto才能觸發他們的增強。通過ControlFlowPointcut實作。

package com.smart.advisor;

public class WaiterDelegate {
    private Waiter waiter;
    public void service(String clientName) {
        waiter.greetTo(clientName);
        waiter.serveTo(clientName);
    }
    public void setWaiter(Waiter waiter) {
        this.waiter = waiter;
    }
}
           

通過spring配置切面:

<!-- 控制流程切面 -->
    <bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
        <constructor-arg type="java.lang.Class"
            value="com.smart.advisor.WaiterDelegate" />
        <constructor-arg type="java.lang.String" value="service" /><!--service 所有的方法都會織入增強-->
    </bean>
    <bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
        p:pointcut-ref="controlFlowPointcut" p:advice-ref="greetingAdvice" />
    <bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="controlFlowAdvisor" p:target-ref="waiterTarget"
        p:proxyTargetClass="true" />
           

複合切點切面:

上面的流程切面,service中所有的方法都會織入增強,如果我們隻想greeTO()織入增強,我們應該如何做呢?這時候就可以使用複合切點切面:流程切點+方法名切點

ComposablePointcut.提供了切點之前複合運算的功能。

通過Pointcuts工具類,可以完成切點的交集,并集運算。

intersection

union

package com.smart.advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.NameMatchMethodPointcut;

public class GreetingComposablePointcut {
   public Pointcut getIntersectionPointcut(){
       ComposablePointcut cp = new ComposablePointcut(); //複合切點
       Pointcut pt1 = new ControlFlowPointcut(WaiterDelegate.class,"service");//流程切點
       NameMatchMethodPointcut pt2 = new NameMatchMethodPointcut();//方法名切點
       pt2.addMethodName("greetTo");
       return cp.intersection(pt1).intersection((Pointcut)pt2);    //注意這裡的方法
   }
}
<!-- 複合切點切面 -->
    <bean id="gcp" class="com.smart.advisor.GreetingComposablePointcut" />
    <bean id="composableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
        p:pointcut="#{gcp.intersectionPointcut}" p:advice-ref="greetingAdvice" />
    <bean id="waiter4" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="composableAdvisor" p:target-ref="waiterTarget"
        p:proxyTargetClass="true" />
           

引介切面:

是引介增強的封裝器;

另外:

前面我們都通過proxyFactoryBean配置代理。Spring中我們使用了BeanPostProcessor 來完成這項工作。

實作類:

自動建立代理:根據名字找到代理的目标類,根據切面中的資訊找到代理的目标類。

<!-- 通過Bean名稱自動建立代理 -->
        <bean
        class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
        p:beanNames="*er" p:interceptorNames="greetingAdvice"
        p:optimize="true"/>
           
<!--通過Advisor自動建立代理-->
    <bean id="regexpAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
        p:patterns=".*greet.*" p:advice-ref="greetingAdvice"  />
    <bean
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"  p:proxyTargetClass="true" />
           

注意:目标類的内部方法調用是不會使用代理類處理的。比如serverto()中調用greetTo 的時候不會被增強、