註解(Annontation)是Java5開始引入的新特徵,是那些插入在源碼中的程序可讀的註釋信息。註解信息不會改變程序的編譯方式和運行方式(反射纔會),實際上若是不使用反射解釋(能夠理解爲解析、提取等,找不到合適的詞來描述這一動做)註解信息,註解就不會對程序有任何影響。java
註解自己是無害(對程序無影響)的,但咱們會經過反射來解釋註解,並影響程序行爲。 註解通常用於IDE靜態檢查代碼是否符合某些規範,有些註解能夠用於生成文檔,應用中用得更多的是能夠動態改變程序行爲的註解,如:Spring中的@Cacheable等,也有一些僅僅只是一個標記(沒有任何屬性)。 實際上註解種類繁多且應用普遍,並且對代碼的侵入性相對較小(不解釋就不生效)。git
註解本質是一個繼承了Annotation的特殊接口,咱們能夠用反射從類、方法、字段、參數等對象中取得它們的信息,若是須要實現註解申明的功能,就須要使用反射API解釋註解信息,根據註解提供的標記(註解自己)、屬性等來動態改變程序行爲,如:Spring的@Cacheable註解,後面咱們會模擬這一過程。實際應用中不太可能在業務代碼中寫一堆反射代碼,因此咱們一般會用動態代理的方式,爲目標類(接口)生成應用了註解的動態代理,來簡化應用過程。程序員
咱們在編寫註解時並不是無章可循,須要藉助一些基礎的註解來實現,這些註解被稱做元註解,java.lang.annotation提供了四種元註解:github
JDK中提供的註解多用於標記(提供給IDE檢查用),通常推薦使用。緩存
註解由於使用方法,因此在框架和庫中被廣爲使用,典型的像Spring、Mybatis等。併發
@Insert
這樣的註解(該註解用於編寫插入SQL語句)來實現業務邏輯,SQL與Java代碼耦合在一塊兒,這跟不使用註解直接把SQL寫在Java代碼中也沒什麼分別了,相比之下寫在XML方便統一管理會更爲合適(以上爲我的愚見,不喜勿噴)。除了幾個元註解,JDK及開源框架中的註解也都是相應的程序員來實現的,有些註解確實很實用,那麼在咱們平常開發中,有些功能和特性不妨用自定義註解來實現,代碼會更優雅,複用程度也會更高(我的以爲相似Cloneable、Serializable這樣的標記接口,換成註解來實現會不會更優雅)。 下面以緩存註解爲例演示自定義註解的定義及解釋(應用)過程。app
定義兩個註解@Cacheable
、@CacheEvict
分別描述緩存和刪除緩存邏輯(只做演示用,完整功能請參考Spring的實現)框架
@Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Cacheable { /** * 緩存前綴 * * @return */ String prefix() default ""; /** * 緩存前綴,至關於prefix的別名,value表示是一個默認屬性(當只有這一個屬性時,能夠省略屬性名) * * @return */ String value() default ""; /** * 緩存版本 * * @return */ int version() default 0; } @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CacheEvict { /** * 緩存前綴 * @return */ String prefix() default ""; /** * 緩存前綴,至關於prefix的別名,value表示是一個默認屬性(當只有這一個屬性時,能夠省略屬性名) * @return */ String value() default ""; /** * 緩存版本 * @return */ int version() default 0; }
咱們有一個業務接口及實現dom
public interface UserService { @Cacheable(prefix = "user", version = 16) String get(long userId); @CacheEvict(prefix = "user", version = 16) void update(long userId, String name); } public class UserServiceImpl implements UserService { @Override public String get(long userId) { System.out.println("執行查詢邏輯!"); // 隨機休眠[0, 256)毫秒模擬程序實際執行過程 try { Thread.sleep(new Random().nextLong() & 255L); } catch (InterruptedException e) { e.printStackTrace(); } return String.format("user-%d", userId); } @Override public void update(long userId, String name) { System.out.println("執行更新邏輯!"); } }
測試一下這個業務實現ide
private UserService service; @Before public void init() { service = new UserServiceImpl(); } @Test public void get() throws Exception { long time = System.currentTimeMillis(); String name = service.get(10086L); System.out.println(String.format("程序執行耗時:%d毫秒", System.currentTimeMillis() - time)); assertEquals("user-10086", name); }
從測試結果來看,方法會執行實現類中的邏輯,性能相對較差(使用休眠模擬,在[0, 256)毫秒範圍內)。
實際我在接口上添加了緩存註解,因此須要使用反射解釋該註解,應用緩存來優化業務接口。
private UserService service; @Before public void init() { service = new UserServiceImpl(); } @Test public void cache() throws NoSuchMethodException { // 假設HashMap是咱們的緩存 HashMap<String, Object> cache = new HashMap<>(); // 假設咱們調用是像下面這樣的 // String name = service.get(10086L); String name = null; // 使用反射獲取方法上的註解 Method method = service.getClass().getDeclaredMethod("get", long.class); Cacheable cacheable = method.getAnnotation(Cacheable.class); if (cacheable != null) { // 解釋該註解裏的配置項 String prefix = cacheable.prefix(); if (prefix.length() == 0) { prefix = cacheable.value(); } // 當設置了緩存鍵 if (prefix.length() > 0) { // 1. 繼續取出version等信息,這裏簡化處理,忽略這兩項 int version = cacheable.version(); // 2. 設置了緩存鍵,因此將方法執行結果緩存(若是緩存中未命中) String key = String.format("%s:%d:%d", prefix, 10086, version); if (cache.containsKey(key)) { name = (String) cache.get(key); } else { name = service.get(10086L); cache.put(key, name); } } } else { name = service.get(10086L); } assertEquals("user-10086", name); }
代碼描述的使用反射解釋註解和應用註解的過程,但在實際開發中,不會在業務代碼中夾雜這麼多的反射代碼,因此咱們把它封裝成動態代理工廠類,簡化業務端代碼。
定義一個動態代理工廠類,封裝動態代理生成過程,動態代理代碼裏解釋了緩存註解,應實現了其聲明的功能。
public class CacheProxyFactory { /** * 僅做測試,這裏不考慮併發狀況 */ private static final HashMap<String, Object> CACHE_STORAGE = new HashMap<>(); public static final <T> T createProxyInstance(T target) { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CacheInvocationHandler(target)); } private static class CacheInvocationHandler<T> implements InvocationHandler { private final T target; public CacheInvocationHandler(T target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object r = null; // 從target和method中提取註解信息 Cacheable cacheable = method.getAnnotation(Cacheable.class); CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class); if (cacheable != null) { r = cache(cacheable, method, args); } else if (cacheEvict != null) { r = remove(cacheEvict, method, args); } else { r = method.invoke(target, args); } return r; } /** * 處理@Cacheable註解 * * @param cacheable * @param method * @param args * @return * @throws InvocationTargetException * @throws IllegalAccessException */ private Object cache(Cacheable cacheable, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { Object r = null; // 解釋該註解裏的配置項 String prefix = cacheable.prefix(); if (prefix.length() == 0) { prefix = cacheable.value(); } // 當設置了緩存鍵 if (prefix.length() > 0) { // 1. 繼續取出version等信息,這裏簡化處理,忽略這兩項 int version = cacheable.version(); // 2. 設置了緩存鍵,因此將方法執行結果緩存(若是緩存中未命中) String key = String.format("%s:%d:%d", prefix, 10086, version); if (CACHE_STORAGE.containsKey(key)) { r = CACHE_STORAGE.get(key); } else { r = method.invoke(target, args); CACHE_STORAGE.put(key, r); } } else { // 應該拋出異常(使用該註解,必須配置value或prefix屬性) } return r; } /** * 處理@CacheEvict註解 * * @param cacheEvict * @param method * @param args * @return * @throws InvocationTargetException * @throws IllegalAccessException */ private Object remove(CacheEvict cacheEvict, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { // 解釋該註解裏的配置項 String prefix = cacheEvict.prefix(); if (prefix.length() == 0) { prefix = cacheEvict.value(); } if (prefix.length() > 0) { int version = cacheEvict.version(); CACHE_STORAGE.remove(String.format("%s:%d:%d", prefix, 10086, version)); } else { // 應該拋出異常(使用該註解,必須配置value或prefix屬性) } return method.invoke(target, args); } } }
測試應用了緩存註解的代理對原業務接口性能的提高
private UserService service; @Before public void init() { service = new UserServiceImpl(); } @Test public void proxy() { // 生成代理 service = CacheProxyFactory.createProxyInstance(service); System.out.println("-- 1 --"); long time = System.currentTimeMillis(); String name = service.get(10086L); // 程序執行耗時:112毫秒 System.out.println(String.format("程序執行耗時:%d毫秒", System.currentTimeMillis() - time)); assertEquals("user-10086", name); System.out.println("-- 2 --"); time = System.currentTimeMillis(); name = service.get(10086L); // 程序執行耗時:0毫秒 System.out.println(String.format("程序執行耗時:%d毫秒", System.currentTimeMillis() - time)); assertEquals("user-10086", name); // 執行更新方法移除緩存(請忽略實際更新邏輯) service.update(10086L, "Peter"); System.out.println("-- 3 --"); time = System.currentTimeMillis(); name = service.get(10086L); // 程序執行耗時:243毫秒 System.out.println(String.format("程序執行耗時:%d毫秒", System.currentTimeMillis() - time)); assertEquals("user-10086", name); }
經過測試結果能夠看出,第二次調用get方法時,直接走了緩存,因此性能有了大幅提高(實際提高效果視緩存的實現方案而定)。而且在執行update方法後,緩存被清空,再次調用get方法時,又從新初始化了緩存,從而實現了完整的@Cacheable和@CacheEvict註解功能。 這套緩存註解僅僅是從概念是模擬了Spring的緩存註解,相比之下,Spring提供了更完整的功能和程序健壯性,因此應用開發中推薦使用。
自定義註解與反射和動態代理是緊密相連的,因此要掌握自定義註解,反射和動態代理技術是前置條件,不瞭解的請閱讀筆者前面的文章
源碼倉庫: