簡單!5分鐘幫你搞懂自定義註解及動態代理

1-自定義註解及使用

註解在學習Java基礎的時候就已經接觸了,但是還是沒有深入一點的學習,況且現在也記不住了。
註解和接口、類一樣,都是屬於數據類型。它可以在變量、方法、類上加載
之所以有反射這類東西,也是因爲註解可以自定義屬性,例如:@MyAnno(name=“hillain”)
註解的作用範圍分別有:源碼、編譯期間、運行期間

  • 源碼期間有效:如String類上的註解@Author,@Since,@See等
    作用:使用命令javadoc命令可以將當前源碼生成幫助文件,可以識別String類上的相關注解,可以用來裝逼。
  • 編譯期間有效:@Override,@Deprecated,@Suppresswarning等
    作用:能夠在我們寫代碼的時候做一些編譯檢查,比如@Override就能夠提示我們該方法是被重寫的,並且如果該方法無該父類方法,則會報錯。
  • 運行期間有效:可以用@Retention來說明自定義註解在運行期間生效
    註解的作用:可以通過反射的方式獲取註解屬性的信息,最簡單的例子就是創建Web工程時使用3.0版本後創建Servlet,類上的註解就是一個配置信息

註解的類型就是以上三種,這裏簡要總結一下:
三種類型:1、編譯檢查 2、(最常用)生成配置 3、生成幫助文檔

那麼如何自定義註解?自定義註解格式如下:

public @interface 註解名稱{
	public 屬性類型 屬性名稱1();
	public 屬性類型 屬性名稱2() default 默認值;
]

這裏我們可以看到,註解類有個@interface,這裏我們可以認爲註解類就是一個接口,只不過這個接口中可以有自己的屬性變量及默認值,當我們在普通的類中使用自定義註解的時候,我們還可以手動修改屬性的值。

說到註解屬性,其支持的屬性類型有:基本數據類型(4類8種)、String、Class、Annotation、枚舉及以上的一維數組類型 —— 這也就是爲什麼註解也算一種數據類型的原因之一

這裏我們根據上述格式手動創建一個註解類,具體代碼如下:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//定義註解的時候,需要通過@Retention說明自定義註解的作用域(Class,Sourse,Runtime)
@Retention(RetentionPolicy.RUNTIME)
//定義註解的時候,需要通過元註解@Target說明自定義註解的目標對象,加在什麼上面,如方法
@Target(ElementType.METHOD)
public @interface MyAnnotation {
	int num() default -1;
	public String name() default "hillain";
}

這裏可能會有疑惑,爲什麼上面要有@Retention和@Target?
其實這些註解都是元註解,JDK 1.5中提供了4個標準的用來對註解類型進行註解的註解類,我們稱之爲 meta-annotation(元註解),具體的元註解有:
@Target、@Retention、@Documented、@Inherited

所以我們用@Retention來說明自定義註解的作用域(Class,Sourse,Runtime),用@Target來說明自定義註解的目標對象,比如加在類上或加在方法上等。
自問:加@Retention的道理我懂,但是有必要加@Target嗎?我不加不還是能修飾方法嗎?也不會報錯啊?
自答:確實,如果說你不動手操作一下,那肯定不會瞭解到@Target的用處,這就給你個例子看看

首先,我們先創建一個自定義註解類,emmm,就用上面那個類吧,裏面就兩個變量,一個int類型的num和一個String類型的name
然後,我們創建一個測試類,代碼如下:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.sun.org.apache.bcel.internal.generic.NEW;
public class MyMethods {
	@MyAnnotation
	public void addUser(){
		System.out.println("增加用戶");
	}
	public void delUser(){
		System.out.println("刪除用戶");
	}
	@MyAnnotation(name="hpf",num=988)
	public void uptUser(){
		System.out.println("更新用戶");
	}
	public void findUser(){
		System.out.println("查詢用戶");
	}
	public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		MyMethods mds = new MyMethods();
		Method[] methods = mds.getClass().getMethods();
		for (Method method : methods) {
			if(method.isAnnotationPresent(MyAnnotation.class)){
				method.invoke(mds);//執行方法
				java.lang.annotation.Annotation[] ans = method.getAnnotations();
				for (java.lang.annotation.Annotation an : ans) {
					System.out.println("name="+((MyAnnotation)an).name());
					System.out.println("num="+((MyAnnotation)an).num()+"\n");
				}
			}
		}
	}
}

可見addUser和uptUser方法加了註解的修飾,其他的方法沒加,運行結果如下:
運行效果
總結:從運行結果可知,我們成功遍歷並且得到了註解的屬性變量,但是如果我們把註解類中的Target刪除了話,那麼再運行就不會有信息出現了。其次,目前學習階段只需要瞭解並且會使用註解來做一些配置就可以了,而且後期開發是很少使用自定義註解的,但我們還是需要了解一下這方面的知識。

2-裝飾者模式與動態代理

這是汽車公司的一款ICar汽車的接口:

public interface ICar {
	public String start(int a,int b);
	public void run();
	public void stop();
}

情況一:汽車公司只給了你這個接口,讓你去開發這款車的功能,你會怎麼寫?
自答:這不是傻瓜問題嗎?如果是我的話,直接實現這個接口寫功能就好啦,直接上代碼。

public class GoogleCar implements ICar{
	//餓漢式單例
	private final static GoogleCar car = new GoogleCar();
	private GoogleCar() {}
	public static GoogleCar getGoogleCar(){
		return car;
	}
	public void ok(){
		System.out.println("主人已入座!");
	}
	@Override
	public String start(int time,int speed) {
		System.out.println("GoogleCar啓動中!");
		return "已設置"+time+"秒後啓動,"+"速度:"+speed+"碼——主人請繫好安全帶!";
	}
	@Override
	public void run() {
		System.out.println("GoogleCar運行中!");
	}
	@Override
	public void stop() {
		System.out.println("GoogleCar停止中!");
	}
}

運行效果如下:
運行效果


情況二:汽車公司給了你汽車的功能系統,也就是上面我們寫的GoogleCar,但是該類不允許你修改,也就說在不破壞原有代碼的基礎上加強功能,你會怎麼做?
自答:這個時候就要用到裝飾者模式了,裝飾者模式就相當於我們自己的車庫,把GoogleCar放進來改裝了一下,既然如此,肯定要有我們自己的類以及GoogleCar的實例了,具體代碼如下👇

public class NewClearCar implements ICar{
	public ICar goodCar = null;
	public NewClearCar() {}
	public NewClearCar(ICar car) {
		//懶漢式單例
		if(goodCar==null)goodCar = car;
	}
	@Override
	public String start(int time,int speed) {
		System.out.println("歡迎來到新能源汽車!");
		return goodCar.start(time,speed);
	}
	@Override
	public void run() {
		goodCar.run();
	}
	@Override
	public void stop() {
		goodCar.stop();
		System.out.println("新能源汽車已關閉,再見!");
	}
}

運行效果如下:
裝飾者模式
這裏順手一提,爲什麼要用裝飾者模式,舉個最簡單的例子,如果你對原有的系統直接修改的話,那麼就算是二次開發,二次開發的前提是我們要有源碼,況且我們二次開發要是修改了什麼數據除了問題,那麼會很麻煩,這是我的理解。


情況三:當汽車公司給你的類中方法有100多個,那就意味着需要增強的方法要寫,不需要增強的方法你也要調用被裝飾對象,你會怎麼做?
自答:這個時候用裝飾者模式就太麻煩了,這時我們就可以使用動態代理來解決這一缺陷,既可以像裝飾者模式那樣裝飾對象,也可以避免被裝飾對象方法過多的問題,而且我們可以直接在測試類中用匿名內部類調用動態代理對象來使用:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
	public static void main(String[] args) {
		//使用動態代理proxy
		ICar car1 = GoogleCar.getGoogleCar();
		ICar car2 = (ICar)Proxy.newProxyInstance(Test.class.getClassLoader(), GoogleCar.class.getInterfaces(), new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				if(method.getName().equalsIgnoreCase("start")){
					System.out.println("歡迎來到新能源汽車!");
					System.out.println(method.invoke(car1, args));
					//System.out.println(Arrays.toString(args));	將args,即參數打印
					System.out.println("自動駕駛已開啓!");
				}else if(method.getName().equalsIgnoreCase("stop")){
					method.invoke(car1, args);
					System.out.println("新能源汽車已關閉,再見!");
				}else{
					method.invoke(car1, args);
				}
				return null;
			}
		});
		car2.start(3,80);car2.run();car2.stop();
	}
}

運行效果如下:
動態代理

3-動態代理實現原理

動態代理的缺點:不論是動態代理和裝飾者模式,都有一個缺陷,那就是除了可重寫的方法,被裝飾對象自帶的方法是無法修改的
  proxy對象通過編寫class文件與增加字節碼的方式來實現動態代理,當你找到了需要裝飾的方法並且做了修改時,proxy對象就會在工作路徑下生成一個class文件,當你打開class文件會看到劈里啪啦一堆符號,但是會發現你要修飾的方法確確實實是寫進去了,這就說明了proxy對象確實是通過增加字節碼的方式來實現動態代理的。
字節碼
這裏也擴展一下類加載器的知識,類加載器包括了三種類型:1、引導性類加載器 2、擴展類加載器 3、應用類加載器

//1、引導性類加載器 - 獲取String類的加載器
ClassLoader classLoader = String.class.getClassLoader();
/*由於String.class、int.class等字節碼文件需要頻繁被加載到內存
	速度必須快,所以底層是用C、C++來實現*/
System.out.println(classLoader);

//2、擴展類加載器 - 獲取ext(extendtion)包下的某個類的字節碼加載器
ClassLoader classLoader2 = sun.security.ec.CurveDB.class.getClassLoader();
System.out.println(classLoader2);

//3、應用類加載器 - 程序員實現的所有的類都屬於應用類
ClassLoader classLoader3 = TestClassLoader.class.getClassLoader();
System.out.println(classLoader3);

引導性類加載器故名思意就是起引導作用的加載器,String、int等變量是需要頻繁的使用並且加載的,所以調用速度要快才行,而底層使用C、C++來加載這類變量的字節碼文件是最快速最理想的選擇了

HPF-自我總結

  註解有的人認爲容易理解,有的人認爲不好理解。其實這個東西我們自己去試一試就知道了,一開始我都不知道爲什麼註解可以定義變量,也不知道定義了變量有什麼用,甚至不知道定義變量該怎麼用,但是後來自己寫個程序運行一下就會理解一些,歸根結底還是自己試一試。
  今天做個標題黨😝,文章內容不好請各位嘴下留情,我只是希望能夠幫到人哦,喜喜
  記住記住記住,不忘初心~方得始終!

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