都已經(jīng)記不起到底何時開始使用Java語言進(jìn)行編程的了裁厅,印象中起碼也有三五年了吧拾徙。想象中自己應(yīng)該要做后臺滞诺,卻陰差陽錯成了Android狗这刷,還好Java仍然陪伴著我(Java:你明明去看了Kotlin3裎)鼻弧。
今天打算寫寫關(guān)于代理模式和注解這方面的內(nèi)容。這兩個簡直是風(fēng)馬牛不相及锦茁,放在一起好像有那么一丟丟別扭攘轩。不過呢,這主要是為了后面的文章做準(zhǔn)備码俩。作為一名偽源碼愛好者度帮,還是讀過不少源碼。其中Android的網(wǎng)絡(luò)請求框架Retrofit就將動態(tài)代理和注解完美了結(jié)合在了一起稿存,后面詳細(xì)講解吧(PS:這些內(nèi)容一年前就已經(jīng)看懂了笨篷,現(xiàn)在只為了記錄)!
1. 代理模式
代理可以分為兩類:靜態(tài)代理和動態(tài)代理瓣履,說實話率翅,Android在使用代理類這方面用的并不是很多,關(guān)于AOP(面向切面編程)這些東西使用頻繁的還是在后臺開發(fā)袖迎。
代理的三個條件:
- 共同接口:代理類和被代理類實現(xiàn)相同的接口
- 真實對象:實現(xiàn)接口安聘,并真正完成某些操作
- 代理對象:實現(xiàn)接口痰洒,對真實對象進(jìn)行代理,可以在其操作前后執(zhí)行其他操作
關(guān)于代理我的理解就是xxx(被代理類)有中間商(代理類)浴韭,還可能賺差價(進(jìn)行操作)丘喻。光說不練好像一點用處都沒有,還是用show you my code吧念颈!
共同接口:
public interface Action {
void doSomething(float value);
}
真實對象:
public class RealDoAction implements Action {
@Override
public void doSomething(float value) {
System.out.println("我是車主我要賣車泉粉,價錢" + value + "元");
}
}
靜態(tài)代理:
public class ActionProxy implements Action {
private Action realDoAction;
public ActionProxy(RealDoAction realDoAction) {
this.realDoAction = realDoAction;
}
@Override
public void doSomething(float value) {
// 被代理類操作執(zhí)行前執(zhí)行的操作
System.out.println("我是平臺,在我這里可以進(jìn)行交易榴芳。");
realDoAction.doSomething(value);
// 被代理類操作完成后執(zhí)行的操作
System.out.println("車賣出去了嗡靡,價錢" + addValue(value) + "元,沒有中間商賺差價窟感。");
}
private float addValue(float value) {
return value + 10;
}
}
// 測試
public static void main(String[] args) {
Action action = new ActionProxy(new RealDoAction());
action.doSomething(100);
}
輸出:
我是平臺讨彼,在我這里可以進(jìn)行交易。
我是車主我要賣車柿祈,價錢100.0元
車賣出去了哈误,價錢110.0元,沒有中間商賺差價躏嚎。
可以看到使用靜態(tài)代理模式可以很方便的擴展原有的功能并且不去修改原代碼蜜自,但是對于需要代理的多個類此時實現(xiàn)卻有些麻煩。為了簡單有效的解決這個問題卢佣,我們可以使用動態(tài)代理來更靈活的方式去實現(xiàn)代理功能重荠。
使用動態(tài)代理的方式也比較簡單,Java提供給我們Proxy.newProxyInstance
可以快速方便實現(xiàn)動態(tài)代理:
// 還是原來的代碼
Action realDo = new RealDoAction();
Action action = (Action) Proxy.newProxyInstance(Action.class.getClassLoader(), // 類加載器
new Class[]{Action.class}, // 被代理類實現(xiàn)的所有接口
new InvocationHandler() { // InvocationHandler具體邏輯寫在這里虚茶,返回值是該方法的返回值戈鲁。接口中有多個方法可以根據(jù)方法名稱判斷
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是平臺,在我這里可以進(jìn)行交易嘹叫。");
method.invoke(realDo, args);
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Float) {
System.out.println("車賣出去了荞彼,價錢" + ((float) args[i] + 10) + "元,沒有中間商賺差價待笑。");
break;
}
}
return null;
}
});
action.doSomething(1000);
看下輸出鸣皂,和靜態(tài)代理一樣:
我是平臺,在我這里可以進(jìn)行交易暮蹂。
我是車主我要賣車寞缝,價錢1000.0元
車賣出去了,價錢1010.0元仰泻,沒有中間商賺差價荆陆。
代理到這里基本上就結(jié)束了,東西不多集侯。
2. 注解
談起注解被啼,可寫的內(nèi)容可就很多了帜消。這里還是簡單來寫吧,省的又是長篇大論浓体。
首先說下可以用在注解上的注解——元注解:
@Retention保留期泡挺,可取值:
- RetentionPolicy.SOURCE 注解只在源碼階段保留,編譯后的class文件不存在該注解命浴。
- RetentionPolicy.CLASS 注解只被保留到編譯進(jìn)行的時候娄猫,編譯后的class文件該注解仍然存在,但是不會被加載到JVM中生闲。
- RetentionPolicy.RUNTIME 注解可以保留到程序運行的時候媳溺,它會被加載進(jìn)入到 JVM 中,所以在程序運行時可以獲取到它們碍讯。
關(guān)于RetentionPolicy.SOURCE和RetentionPolicy.CLASS這兩者都可以使用AbstractProcessor來在編譯時生成代碼悬蔽,其主要不同點在于編譯后的class文件中是否存在該注解。也可以理解為是方便給IDE看還是給程序員看捉兴。
@Target注解的作用域蝎困,可取值:
- ElementType.ANNOTATION_TYPE 可以給一個注解進(jìn)行注解
- ElementType.CONSTRUCTOR 可以給構(gòu)造方法進(jìn)行注解
- ElementType.FIELD 可以給屬性進(jìn)行注解
- ElementType.LOCAL_VARIABLE 可以給局部變量進(jìn)行注解
- ElementType.METHOD 可以給方法進(jìn)行注解
- ElementType.PACKAGE 可以給一個包進(jìn)行注解
- ElementType.PARAMETER 可以給一個方法內(nèi)的參數(shù)進(jìn)行注解
- ElementType.TYPE 可以給一個類型進(jìn)行注解,比如類轴术、接口难衰、枚舉
- ElementType.TYPE_PARAMETER和ElementType.TYPE_USE(1.8新增)它們都可以限制哪個類型可以進(jìn)行注解弯囊。能在局部變量岂津、泛型類吕晌、父類和接口的實現(xiàn)處使用,甚至能在方法上聲明異常的地方使用彼宠。(具體用途不詳。弟塞。)
@Documented有關(guān)注釋的注解凭峡。
@Inherited指可繼承注解,表示某個類的父類擁有該注解决记,子類如果沒有其他注解的話摧冀,那么該子類會繼承父類的注解。
@Repeatable(1.8新增)我的理解是為了簡化一個屬性擁有多個相同注解而新增的注解系宫,這里面可以放多個注解索昂。(解釋不太清楚,用的也不多)
上面講了5個元注解扩借,其實常用的也就@Retention和@Target椒惨,這里面可以根據(jù)@Retention可以分為編譯時注解和運行時注解,下面分別寫個例子潮罪。
2.1 編譯時注解
關(guān)于編譯時注解昨天基本上花費了一整天康谆,原本Eclipse中的可以執(zhí)行但是換成了IDEA就不能使用了领斥。花費了一天時間沃暗,查找原因(主要還是不想用老大哥Eclipse進(jìn)行展示)月洛。下面看下例子:
- 創(chuàng)建注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS) // 這里也可以改為RetentionPolicy.SOURCE,只不過在class文件中看不到這個注解
public @interface TestAnnotation {
String value() default "";
}
- 繼承AbstractProcessor實現(xiàn)編譯時生成代碼
@SupportedAnnotationTypes("com.nick.demo.annotation.TestAnnotation")// 要支持的注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 版本
public class Processor extends AbstractProcessor {
// 文件創(chuàng)建器
private Filer filer;
// 消息輸出者
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 初始化獲取文件創(chuàng)建器和消息輸出
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 根據(jù)注解獲取所有的Element
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(TestAnnotation.class);
// 遍歷
for (Element element : elements) {
// 如果類型是Class描睦,也就是注解的作用域是ElementType.TYPE
if (element.getKind() == ElementKind.CLASS) {
// 轉(zhuǎn)換成TypeElement
TypeElement typeElement = (TypeElement) element;
Name simpleName = typeElement.getSimpleName();
// 輸出名稱膊存,只為測試而用
messager.printMessage(Diagnostic.Kind.NOTE, simpleName);
// 輸出全部名稱
messager.printMessage(Diagnostic.Kind.NOTE, typeElement.getQualifiedName());
// 創(chuàng)建文件
createFile(typeElement.getQualifiedName().toString(), element.getAnnotation(TestAnnotation.class).value());
}
}
return true;
}
private void createFile(String className, String output) {
StringBuilder cls = new StringBuilder();
// 獲得com.xxx.A的包名,com.xxx
String[] strings = className.split("\\.");
StringBuilder packageName = new StringBuilder();
for (int i = 0; i < strings.length - 1; i++) {
packageName.append(strings[i]);
if (i != strings.length - 2) {
packageName.append(".");
}
}
// 代碼的拼接
cls.append("package " + packageName + ";\n\npublic class ")
.append(strings[strings.length - 1] + output)
.append(" {\n public static void main(String[] args) {\n")
.append(" System.out.println(\"")
.append(output)
.append("\");\n }\n}");
// 輸出這段代碼
messager.printMessage(Diagnostic.Kind.NOTE, cls.toString());
try {
// 通過文件創(chuàng)建器創(chuàng)建SourceFile
JavaFileObject sourceFile = filer.createSourceFile(className + output);
Writer writer = sourceFile.openWriter();
writer.write(cls.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
在與java平級目錄下創(chuàng)建resources文件夾忱叭,并在其目錄下創(chuàng)建META-INF文件隔崎,接著在META-INF創(chuàng)建services,最后在services文件目錄下創(chuàng)建文件javax.annotation.processing.Processor韵丑,內(nèi)容為自定義的Processer的類全名爵卒。如下圖所示:
內(nèi)容.png
- 將java文件和resources文件打包成jar,idea可以通過Project Structure-> Artifacts 添加JAR-> From module撵彻,選擇module并且記得將resources也添加:
- 這一步很關(guān)鍵钓株,我們創(chuàng)建了編譯時注解的代碼的jar,我們需要告訴ide在編譯的時候執(zhí)行我這個jar陌僵,所以需要將其添加到Compiler的Annotation Processors中:
-
將jar導(dǎo)入項目中轴合,并且使用我們剛才創(chuàng)建的注解,這里一定要注意把原項目定義的注解刪掉碗短,使用jar中的注解受葛,不然不會生成代碼:
添加注解.png 編譯,可以看到Messages中有我們打印的日志偎谁。編譯成功可以在build/classes/main/目錄下找到自定義注解生成的class文件总滩,同時在generated目錄下還有java文件的生成:
OK,編譯時注解這里基本上就講完了巡雨。Android好多優(yōu)秀的框架都使用的編譯時注解框架闰渔,例如Android的Databinding框架,butterknife等等铐望。
2.2 運行時注解
運行時注解就比較簡單了冈涧,下面還是看下例子:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String value() default "";
}
@TestAnnotation("123456789")
public class Main {
public static void main(String[] args) {
// 獲得類的TestAnnotation注解
TestAnnotation annotation = Main.class.getAnnotation(TestAnnotation.class);
if (annotation != null) {
// 將值輸出
System.out.println(annotation.value());
}
}
}
運行輸出:
123456789
好像沒什么可以說的,基于運行時注解的框架也不少正蛙,例如EventBus督弓、Retrofit等等跟畅。
The end
寫完收工咽筋,關(guān)于代理和注解就寫這么多吧。希望能在年前將Retrofit的分析寫完奸攻,加油了辐赞。