轉(zhuǎn)載地址:http://kentkwan.iteye.com/blog/739513
Part I
我并不是在賣弄自己的英語(yǔ)有多少的了不起,只不過對(duì)Annotation這一次的解釋真的很懊惱,“注解”和“注釋”這兩個(gè)對(duì)Annotation的翻譯我聽著不爽,所以全文都用Annotation來表示。
Part II
相信java的開發(fā)人員對(duì)Annotation這個(gè)名詞一定是非常的熟悉了,如今許多優(yōu)秀的開源框架,都會(huì)提供了Annotation的支持。如SPRing、Hibernate、JUnit4等。但是這又是為什么那么多的程序員都熱衷于Annotation的使用呢?我個(gè)人的原因是因?yàn)樗_實(shí)的簡(jiǎn)化了我們的操作,雖然這樣做使得代碼和配置的分離難以實(shí)現(xiàn)。
Part III
下面我們就用一個(gè)權(quán)限控制的例子來說明一下,如何使用Annotation來簡(jiǎn)化我們的開發(fā)。
預(yù)期功能:
1. 對(duì)于每個(gè)用戶都設(shè)定一個(gè)對(duì)應(yīng)的權(quán)限。
2. 每個(gè)Dao的操作都加入對(duì)權(quán)限的檢查。權(quán)限不足則拋出安全異常。
思考:
1. Dao層的方法只關(guān)心Dao的操作,對(duì)于權(quán)限的檢查則不需要關(guān)心。因此我們可以用AOP來實(shí)現(xiàn)對(duì)權(quán)限的檢查(在Java中使用動(dòng)態(tài)代理來實(shí)現(xiàn)),實(shí)現(xiàn)權(quán)限檢查和Dao操作的解耦。
2. 每個(gè)用戶都要有相應(yīng)的權(quán)限,而且每個(gè)用戶的操作都是在不同的線程上進(jìn)行,因?yàn)槲覀儽仨氁峁┮粋€(gè)用戶的權(quán)限上下文(RoleContext)來提供對(duì)權(quán)限的設(shè)置和獲取。
3. 對(duì)于Dao層的實(shí)現(xiàn)可以采用面向接口的編碼方式,實(shí)現(xiàn)各層之間的解耦。由于每個(gè)Dao層所對(duì)應(yīng)的實(shí)現(xiàn)類只有一個(gè),因此,我們可以把實(shí)現(xiàn)類的信息作為元數(shù)據(jù)寫入Dao接口中,所以這里最適合用Annotation來實(shí)現(xiàn)。
4. Dao層的方法所需要的權(quán)限信息與實(shí)現(xiàn)無關(guān),因此這里也可以把權(quán)限的信息作為方法的元數(shù)據(jù)寫入,所以這里也十分適合用Annotation來實(shí)現(xiàn)。
Part IV
首先我們把項(xiàng)目基本的架子搭建:
package com.gzmu.annotation.dao;public interface BaseDao { }package com.gzmu.annotation.dao;import com.gzmu.annotation.annotation.Implement;import com.gzmu.annotation.annotation.Permission;import com.gzmu.annotation.dao.impl.UserDaoImpl;import com.gzmu.annotation.util.Role;@Implement(UserDaoImpl.class)public interface UserDao extends BaseDao {@Permission({Role.ADMINISTRATOR, Role.SYSTEM})void save();@Permission(Role.SYSTEM)void delete();@Permission({Role.USER, Role.ADMINISTRATOR, Role.SYSTEM})void query();}package com.gzmu.annotation.dao.impl;import com.gzmu.annotation.dao.UserDao;public class UserDaoImpl implements UserDao {@Overridepublic void save() {System.out.println("UserDaoImpl.save()");}@Overridepublic void delete() {System.out.println("UserDaoImpl.delete()");}@Overridepublic void query() {System.out.println("UserDaoImpl.query()");}}RoleContext作為一個(gè)提供用戶權(quán)限上下文的單元存在,使用枚舉來實(shí)現(xiàn)單例模式,ThreadLocal提供了對(duì)當(dāng)前線程權(quán)限數(shù)據(jù)的訪問。
package com.gzmu.annotation.context;import com.gzmu.annotation.util.Role;public enum RoleContext {INSTANCE;private ThreadLocal<Role> role = new ThreadLocal<Role>();public Role getCurrentRole() {return role.get();}public void setCurrentRole(Role role) {this.role.set(role);}}Implment用來指定Dao接口對(duì)應(yīng)的實(shí)現(xiàn)類。
package com.gzmu.annotation.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import com.gzmu.annotation.dao.BaseDao;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Implement {Class<? extends BaseDao> value();}Permission用于指定Dao層的方法的可訪問的人員的訪問權(quán)限。
package com.gzmu.annotation.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import com.gzmu.annotation.util.Role;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Permission {Role[] value();}到這里,這個(gè)基本的架子就搭建完成了。接下來,我們就要開始使用動(dòng)態(tài)代理、反射以及Annotation來實(shí)現(xiàn)對(duì)權(quán)限的檢查。
Part V
下面我們就要詳細(xì)的解釋一下以下的代碼:
DaoProxyFactory.newRoleDaoProxy():
1. 我們提供一個(gè)簡(jiǎn)單的工廠,用于生產(chǎn)一個(gè)代理對(duì)象。傳入一個(gè)需要代理的接口,用于產(chǎn)生實(shí)現(xiàn)該接口的代理對(duì)象。
2. 由于我們的接口上使用Implement這個(gè)Annotation來指定這個(gè)接口所對(duì)應(yīng)的實(shí)現(xiàn)類,所以我們可以獲取這個(gè)實(shí)現(xiàn)類會(huì)創(chuàng)建一個(gè)實(shí)際被代理的對(duì)象。
RoleInvocationHandler
1. 顧名思義,這個(gè)類就是用來做權(quán)限控制的,這個(gè)類實(shí)現(xiàn)了InvocationHandler。
2. 因?yàn)槲覀円呀?jīng)在接口上定義了哪些方法對(duì)應(yīng)哪些被允許執(zhí)行這個(gè)方法的權(quán)限,因此我們可以通過method.getAnnotation(Permission.class)這個(gè)方法來獲得權(quán)限的信息。
3. 迭代方法的允許權(quán)限,并與當(dāng)前線程用戶的權(quán)限做比較,如果發(fā)現(xiàn)兩者相等,說明當(dāng)前用戶的權(quán)限與方法執(zhí)行的權(quán)限一致,因此跳出循環(huán),執(zhí)行outter標(biāo)簽后面的方法,允許用戶執(zhí)行。
4. 迭代完成后,當(dāng)前線程用戶的權(quán)限沒有與方法中定義的權(quán)限一致,說明用戶無權(quán)執(zhí)行這樣的操作,因此跑出安全異常。
package com.gzmu.annotation.util;import java.lang.annotation.AnnotationFormatError;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import com.gzmu.annotation.annotation.Implement;import com.gzmu.annotation.annotation.Permission;import com.gzmu.annotation.context.RoleContext;import com.gzmu.annotation.dao.BaseDao;public abstract class DaoProxyFactory {@SuppressWarnings("unchecked")public static <T> T newRoleDaoProxy(Class<T> dao) {Implement implAnnotation = dao.getAnnotation(Implement.class);if (implAnnotation == null)throw new AnnotationFormatError("該接口未定義實(shí)現(xiàn)類的注解");BaseDao implClass = null;try {implClass = implAnnotation.value().newInstance();} catch (Exception e) {throw new RuntimeException("該接口所定義的實(shí)現(xiàn)類不能被實(shí)例化", e);}return (T) Proxy.newProxyInstance(DaoProxyFactory.class.getClassLoader(),new Class<?>[] { dao },new RoleInvocationHandler(implClass));}private static final class RoleInvocationHandler implements InvocationHandler {private BaseDao target;public RoleInvocationHandler(BaseDao target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Permission permitAnnotation = method.getAnnotation(Permission.class);outter:if (permitAnnotation != null) {Role currentRole = RoleContext.INSTANCE.getCurrentRole();for (Role permitRole : permitAnnotation.value()) {if (permitRole.equals(currentRole))break outter;}throw new SecurityException("當(dāng)前用戶不允許執(zhí)行此操作");}return method.invoke(target, args);}}}Part VI
通過這個(gè)例子,我們可以看到,用Annotation來簡(jiǎn)化我們的開發(fā)是如此的簡(jiǎn)單,世界是如此的美好。很多的程序員都覺得學(xué)習(xí)Annotation是一種負(fù)擔(dān),或者說xml可以完全取代Annotation的存在。但是我認(rèn)為,一個(gè)事物的存在,必然有他的價(jià)值,沒有任何的一個(gè)事物是能夠完全取代另外一個(gè)事物。與其在作無謂的爭(zhēng)論,不如花時(shí)間去研究如何更好的利用?而且Annotation的隊(duì)伍這個(gè)在不斷的壯大,這就是一種最好的證明。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注