1. 程式人生 > >設計模式---代理模式,從例項看靜態代理,動態代理,CGLIB

設計模式---代理模式,從例項看靜態代理,動態代理,CGLIB

前言

  最近完成了自己的個人部落格專案,要繼續學習Spring了,AOP用的是動態代理,今天特地好好理解一下代理模式

路線

  • 靜態代理
  • jdk動態代理
  • CGLIB動態代理

寫在前面

  代理模式和裝飾器模式,實現路線都是實現特地的介面,然後增加一些功能,那麼它們的重要區別在哪呢?職能!,裝飾器模式主要用於增強方法,而代理模式主要用於控制。舉幾個控制的例子,比如JDBC做事務,是否需要開啟事務,可以用代理。類似下面的.

案例一:JDBC 例子

在這裡插入圖片描述 三部分

  • interface task 代表一個介面
  • Proxy 代表代理類
  • RealTask代表真正進行操作的類

實現:

public interface
task { void doSomething(); } public class RealTask implements task { @Override public void doSomething() { System.out.println("正在處理...."); } } public class Proxy implements task { private task task; public Proxy(task task) { this.task = task; } @Override
public void doSomething() { System.out.println("開啟事務"); task.doSomething(); System.out.println("提交事務"); } }

測試:

public class Main {

    public static void main(String[] args) {
        task task = new RealTask();
        Proxy proxy = new Proxy(task);
        proxy.
doSomething(); } } ----output---- 開啟事務 正在處理.... 提交事務

解釋:   首先,最終doSomething的是誰?是RealTask,雖然這裡呼叫的是Proxy.doSomething方法,但是最終還是由RealTask來執行,它只是在這個基礎上做了兩件額外的事情,開啟事務和提交事務,它沒有侵入到doSomeThing這個任務方法裡面去----意思就是說,開啟事務和你執行的各種資料庫連線sql操作有關係嗎?自然是沒有的。Proxy就像是一箇中介,然後對目標要乾的事情加以控制。再想想裝飾器模式,它是侵入到了doSomething裡面。還不明白啥叫控制?再來個例子。

案例二:控制被執行的次數

public interface hello {
    void sayHello();
}

public class RealHello implements hello {
    @Override
    public void sayHello() {
        System.out.println("Hello");
    }
}

public class ProxyHello implements hello {
    private int time;
    private hello hello;

    @Override
    public void sayHello() {
        if (hello == null) {
            hello = new RealHello();
        }

        if (this.time < 5) {
            hello.sayHello();
        }
        this.time++;
    }
}

測試:

public class Main {

    public static void main(String[] args) {
        hello hello = new RealHello();
        hello proxy = new ProxyHello();
        for (int i = 0; i < 10; i++) {
            proxy.sayHello();
        }
    }
}

----output----
Hello
Hello
Hello
Hello
Hello

  這次的控制就比較明顯了,本來執行10次,因為有了Proxy,當執行次數到達5次的時候,就不會再呼叫hello方法,代理控制沒有侵入到sayHello把?

動態代理

瞭解了什麼是代理模式,代理模式是幹嘛呢,它和裝飾器模式的區別在哪,之後呢,我們還要了解啥叫靜態代理,啥叫動態代理。

  • 靜態代理:靜態 就是說在編譯期就已經知道了,生成了class檔案,我們知道某一個代理是為了某一個類的物件服務的。
  • 動態代理:動態 是說,當有大量的或者需要在執行期間確定代理行為的時候,就要用到這裡的動態。我們也不用寫很多的代理類了

JDK 動態代理

幾個步驟

  • 被代理物件介面
  • 被代理物件
  • 處理Handler,處理代理過程
  • Proxy的靜態方法,建立代理類
  • 通過代理類執行被代理的方法
public class Handler implements InvocationHandler {
    private Object target;
    private int time;

    public Handler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (this.time < 5) {
            method.invoke(target, args);
            this.time++;
        }
        return null;
    }

    public Object getProxy() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
}

    public static void main(String[] args) {
        Hello hello = new RealHello();
        Handler handler = new Handler(hello);

        Hello proxy = (Hello) handler.getProxy();
        for (int i = 0; i < 10; i++) {
            proxy.sayHello();
        }
    }
    
----output----
Hello
Hello
Hello
Hello
Hello

實際引用–事務處理

  看了上面的例子,我們用一個實際應用來演示上面的案例.Demo如下:

public interface ArticleService {
	void newArticle(ArticleForm form, User user) throws NewArticleException;
}

被代理類實現:

@Override
    public void newArticle(ArticleForm form, User user) throws NewArticleException {
//        try {
        Article article = Form2BeanUtils.form2Article(form);
        int userId = user.getUserId();

//            JDBCUtils.startTransaction();
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        Set<String> tagNames = form.getTags();
        articleService.createTags(tagNames, conn);
        Set<String> categories = form.getCategories();
        articleService.createCategories(categories, conn);

        // 獲取新舊合併的的id號
        List<Integer> categoriesIds = categoryDao.getIds(categories, conn);
        List<Integer> tagIds = tagDao.getIds(tagNames, conn);

        //建立連線關係1:n user_article, m:n  article_categories, m:n article_tags
        BigInteger article_id = articleDao.createArticle(article, userId, conn);
        if (tagIds.size() != 0) {
            articleDao.joinTag(article_id, tagIds, conn);
        }
        if (categoriesIds.size() != 0) {
            articleDao.joinCategories(article_id, categoriesIds, conn);
        }
        /*    JDBCUtils.commit();
        } catch (Exception e) {
            try {
                e.printStackTrace();
                JDBCUtils.rollback();
                throw new NewArticleException(e);
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                JDBCUtils.release();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }*/
    }

處理類:

public class TransactionHandler implements InvocationHandler {
    private Object target;

    public TransactionHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            JDBCUtils.startTransaction();
            method.invoke(target, args);
            JDBCUtils.commit();
        } catch (Exception e) {
            try {
                e.printStackTrace();
                JDBCUtils.rollback();
                throw new NewArticleException(e);
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                JDBCUtils.release();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}

使用:

TransactionHandler handler = new TransactionHandler(articleService);
            ArticleService service = (ArticleService) handler.getProxy();
            service.newArticle(form, user);
//            articleService.newArticle(form, user);

注意: 註釋部分是我之前沒有用動態代理的方法,這樣,事務每次執行就能複用這段開始事務,提交事務,而不用大量的寫提交事務等語句。還是挺方便的~

CGLIB

CGLIB底層實現是ASM修改位元組碼,這個比較厲害了,我就從應用的基礎上寫個簡單的例子,給自己留下一點印象:實現和上面一樣的功能。

public class hello {

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

public class MyInterceptor implements MethodInterceptor {
    private int time;

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        if (this.time < 5) {
            proxy.invokeSuper(obj, args);
            this.time++;
        }
        return null;
    }
}

public class App {
    public static void main(String[] args) {
        // 增強物件
        Enhancer enhancer = new Enhancer();
        //設定父類
        enhancer.setSuperclass(hello.class);

        // 設定攔截器
        Callback interceptor = new MyInterceptor();
        enhancer.setCallback(interceptor);

        hello hello = (hello) enhancer.create();
        for (int i = 0; i < 10; i++) {
            hello.sayHello();
        }
    }
}

總結

  • CGLIB和JDK 動態代理兩者的區別:
    • 實現角度: 後者需要實現介面,而CGLIB不需要,直接使用的super,當沒有介面的時候就可以用CGLIB了
    • 關係角度: 後者實現的代理Proxy,更像是被代理物件的兄弟,屬於兄弟關係。而CGLIB就像是繼承關係。
    • 效能方面: 後者的實現是用的反射,建立物件速度優於CGLIB,而CGLIB雖然建立物件慢了點,但是它的方法執行速度卻很快。(看的人家的,未驗證)