AOP 理解

                                                 Spring AOP 解析

先說問題:

  • * Spring AOP用的是哪種設計模式?
  • * 談談你對代理模式的理解?
  • * 靜態代理和動態代理有什麼區別?
  • * 如何實現動態代理?
  • * Spring AOP中用的是哪種代理技術?

 

1. 什麼是 Spring AOP?

 簡單的說面向切面編程 ,這句話 可以說毫無意義。

AOP 實現原理就是代理模式。

簡單說下代理:

明星都有經紀人,明星最重要的一件事就是唱歌,拍電影,參加綜藝,其他事他不用關注,比如唱歌前可能需要和其他人談合作,還要佈置場地,唱歌后還要收錢等等,這些統統交給他對應的經紀人去做。每個人各司其職,靈活組合,達到一種可配置的、可插拔的程序結構。 AOP實現了代理模式。

什麼是代理模式?

代理模式的核心是通過代理,控制對象的訪問。

設計思路,定義一個抽象類,代理類和真實類都去實現它。

真實類:實現抽類,定義真實類所要實現的業務邏輯,供代理類調用。它只關注真正的業務邏輯,比如歌星唱歌。

代理類:實現抽象類,是真實類的代理,通過真實類的業務邏輯方法來實現抽象方法,並在前後可以附加自己的操作,比如談合同,佈置場地,收錢等等。

代理模式分爲靜態代理和動態代理。靜態代理是我們自己創建一個代理類,而動態代理是程序自動幫我們生成一個代理,我們就不用管了。下面我詳細介紹一下這兩種代理模式。

3. 靜態代理模式

靜態代理是採用 代理類和實現類實現接口 的設計思路,靜態代理就是事先我們創建好一個代理類。

/**
* 明星接口類
* @date 2018-12-07
*/
public interface Star {

   /**
    * 唱歌方法
    */
   void sing();

}

 

代理類實現接口

public class ProxyStar implements Star {

    /**
     * 接收真實的明星對象
     */
    private Star star;

    /**
     * 通過構造方法傳進來真實的明星對象
     * @param star star
     */
    public ProxyStar(Star star) {
        this.star = star;
    }

    @Override
    public void sing() {
        System.out.println("代理先進行談判……");
        // 唱歌只能明星自己唱
        this.star.sing();
        System.out.println("演出完代理去收錢……");
    }

 }

真實類實現接口

public class RealStar implements Star {

    @Override
    public void sing() {
        System.out.println("明星本人開始唱歌……");
    }
 }

測試靜態代理

public class Client {

    /**
     * 測試靜態代理結果
     * @param args args
     */
    public static void main(String[] args) {
        Star realStar = new RealStar();
        Star proxy = new ProxyStar(realStar);

        proxy.sing();
    }
 }

4. JDK 動態代理

動態代理是動態生成代理,JDK動態代理是採用 反射機制,動態生成代理類

public class JdkProxyHandler {

    /**
     * 用來接收真實明星對象
     */
    private Object realStar;

    /**
     * 通過構造方法傳進來真實的明星對象
     *
     * @param star star
     */
    public JdkProxyHandler(Star star) {
        super();
        this.realStar = star;
    }

    /**
     * 給真實對象生成一個代理對象實例 Proxy.newProxyInstance()
     * 方法,該方法接收三個參數:第一個參數指定當前目標對象使用的類加載器
     * ,獲取加載器的方法是固定的;
     * 第二個參數指定目標對象實現的接口的類型;
     * 第三個參數指定動態處理器,執行目標對象的方法時,會觸發事件處理器的方法。
     * @return Object
     */
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(realStar.getClass().getClassLoader(), realStar.getClass().getInterfaces(), (proxy, method, args) -> {
            System.out.println("代理先進行談判……");
            // 唱歌需要明星自己來唱
            Object object = method.invoke(realStar, args);
            System.out.println("演出完代理去收錢……");

            return object;
            });
    }
}

roxy.newProxyInstance() 方法,該方法接收三個參數:第一個參數指定當前目標對象使用的類加載器,獲取加載器的方法是固定的;第二個參數指定目標對象實現的接口的類型;第三個參數指定動態處理器,執行目標對象的方法時,會觸發事件處理器的方法。

測試JDK 動態代理

public class Client {

   /**
    * 測試JDK動態代理結果
    * @param args args
    */
   public static void main(String[] args) {
       Star realStar = new RealStar();
       // 創建一個代理對象實例
       Star proxy = (Star) new JdkProxyHandler(realStar).getProxyInstance();

       proxy.sing();
   }
}

JDK 動態代理做一個簡單的總結:相對於靜態代理,JDK 動態代理大大減少了我們的開發任務,同時減少了代理類對業務接口的依賴,降低了耦合度。JDK 動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler 生成代理。但是 JDK 動態代理有個缺憾,或者說特點:JDK 實現動態代理需要實現類通過接口定義業務方法

5. CGLIB 動態代理

JDK 實現動態代理需要實現類通過接口定義業務方法,那對於沒有接口的類,如何實現動態代理呢,這就需要 CGLIB 了。

CGLIB 採用了非常底層的字節碼技術,其原理是通過字節碼技術爲一個類創建子類,並在子類中採用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。但因爲採用的是繼承,所以不能對final修飾的類進行代理

CGLIBHandler生成代理

public class CglibProxyHandler implements MethodInterceptor {

    /**
     * 維護目標對象
     */
    private Object target;

    public Object getProxyInstance(final Object target) {
        this.target = target;
        // Enhancer類是CGLIB中的一個字節碼增強器,它可以方便的對你想要處理的類進行擴展
        Enhancer enhancer = new Enhancer();
        // 將被代理的對象設置成父類
        enhancer.setSuperclass(this.target.getClass());
        // 回調方法,設置攔截器
        enhancer.setCallback(this);
        // 動態創建一個代理類
        return enhancer.create();
    }

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

        System.out.println("代理先進行談判……");
        // 唱歌需要明星自己來唱
        Object result = methodProxy.invokeSuper(object, args);
        System.out.println("演出完代理去收錢……");
        return result;
    }
 }

使用CGLIB 需要實現 MethodInterceptor 接口,並重寫intercept 方法,在該方法中對原始要執行的方法前後做增強處理,該類的代理對象可以使用代碼中的字節碼增強器來獲取

測試CGLib動態代理

public class Client {

    /**
     * 測試Cglib動態代理結果
     * @param args args
     */
    public static void main(String[] args) {
        Star realStar = new RealStar();
        Star proxy = (Star) new CglibProxyHandler().getProxyInstance(realStar);

        proxy.sing();
    }
 }

 

AOP總結

CGLIB 創建的動態代理對象比 JDK 創建的動態代理對象的性能更高,但是 CGLIB 創建代理對象時所花費的時間卻比 JDK 多得多。所以對於單例的對象,因爲無需頻繁創建對象,用 CGLIB 合適,反之使用JDK方式要更爲合適一些。同時由於 CGLIB 由於是採用動態創建子類的方法,對於final修飾的方法無法進行代理。

6. Spring AOP 採用哪種代理?

看源碼

 * Copyright 2002-2015 the original author or authors.

package org.springframework.aop.framework;

import java.io.Serializable;
import java.lang.reflect.Proxy;

import org.springframework.aop.SpringProxy;

/**
 * Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy
 * or a JDK dynamic proxy.
 *
 * <p>Creates a CGLIB proxy if one the following is true for a given
 * {@link AdvisedSupport} instance:
 * <ul>
 * <li>the {@code optimize} flag is set
 * <li>the {@code proxyTargetClass} flag is set
 * <li>no proxy interfaces have been specified
 * </ul>
 *
 * <p>In general, specify {@code proxyTargetClass} to enforce a CGLIB proxy,
 * or specify one or more interfaces to use a JDK dynamic proxy.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since 12.03.2004
 * @see AdvisedSupport#setOptimize
 * @see AdvisedSupport#setProxyTargetClass
 * @see AdvisedSupport#setInterfaces
 */
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

	/**
	 * Determine whether the supplied {@link AdvisedSupport} has only the
	 * {@link org.springframework.aop.SpringProxy} interface specified
	 * (or no proxy interfaces specified at all).
	 */
	private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
		Class<?>[] ifcs = config.getProxiedInterfaces();
		return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
	}

}

是否使用 CGLIB 是在代碼中進行判斷的:

判斷條件是 config.isOptimize()、config.isProxyTargetClass() 和 hasNoUserSuppliedProxyInterfaces(config)。

config.isOptimize() 與 config.isProxyTargetClass()默認返回都是 false

hasNoUserSuppliedProxyInterfaces(config)的結果決定了。

hasNoUserSuppliedProxyInterfaces(config) 就是在判斷代理的對象是否有實現接口,有實現接口的話直接走 JDK 分支,即使用 JDK 的動態代理。

所以基本上可以總結出 Spring AOP 中的代理使用邏輯了:如果目標對象實現了接口,默認情況下會採用 JDK 的動態代理實現 AOP;如果目標對象沒有實現了接口,則採用 CGLIB 庫,Spring 會自動在 JDK 動態代理和 CGLIB 動態代理之間轉換。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章