設計模式---代理模式,從例項看靜態代理,動態代理,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雖然建立物件慢了點,但是它的方法執行速度卻很快。(看的人家的,未驗證)