實現之前,先簡單介紹下業務:
訪問控制列表ACT(Access Control List),當訪問者訪問資源時,對訪問的權限進行判斷是否可以訪問:
1)權限級別: Boss(老闆);Manager(經理);Accountant(會計);Employee(員工);
2)資源文件: Achievement Document(業務統計文件);Salary Document(薪酬信息文件);
Public Document(公共文件);
3)控制邏輯: I:老闆可以訪問任何文件;
II:經理可以訪問員工的業務統計文件;
III:會計可以訪問任何人的薪酬信息文件;
IV:非老闆和會計無法訪問別人的薪酬信息文件,只可以訪問屬於自己的薪酬信息文件;
V: 非老闆和經理無法訪問別人的業務統計文件,只可以訪問屬於自己的業務統計文件;
VI: 公共文件任何人都可以訪問;
代碼:
首先寫出權限級別的枚舉、註解和實例
public enum PersonLevel{ Boss, Manager, Employee, Accountant,; }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Level { PersonLevel level() default PersonLevel.Employee; }
public class Personnel { private String name; public Personnel(String name) { this.name = name; } public String getName() { return name; } public void access(Document document){ Level level = this.getClass().getAnnotation(Level.class); boolean access = level.level().access(this, document); if(access){ document.canAccess(); }else { document.canNotAccess(); } } } @Level(level = PersonLevel.Boss) class Boss extends Personnel{ public Boss(String name) { super(name); } } @Level(level = PersonLevel.Manager) class Manager extends Personnel{ public Manager(String name) { super(name); } } @Level() class Employee extends Personnel{ public Employee(String name) { super(name); } } @Level(level = PersonLevel.Accountant) class Accountant extends Personnel{ public Accountant(String name) { super(name); } }
員工四個權限級別實例,員工內部有私有屬性name表示員工名字,員工內部方法access用來訪問文件;
訪問方法先通過反射獲取訪問者的權限級別註解,在調用權限級別的控制邏輯判斷訪問者是否有權限訪問該文件:
如果有權限則調用文件canAccess方法展示文件內容;
如果沒有權限則調用文件canNotAccess方法提示訪問者;
所以要改造一下級別枚舉PersonLevel爲其添加控制邏輯:
public enum PersonLevel{ Boss{ @Override boolean access(Personnel personnel, Document document) { return true; } }, Manager { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || category == SourceCategory.Achievement || personnel == document.getPersonnel(); } }, Employee { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || personnel == document.getPersonnel(); } }, Accountant { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || category == SourceCategory.Salary || personnel == document.getPersonnel(); } },; abstract boolean access(Personnel personnel, Document document); }
接下來在寫出文件的類型枚舉、註解和實例
public enum SourceCategory{ // 薪酬信息, 業務統計, 公共信息 Salary, Achievement, Public,; }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Source { SourceCategory category() default SourceCategory.Public; }
public class Document { private Personnel personnel; private String title; private String content; public Document(Personnel personnel, String title, String content) { this.personnel = personnel; this.title = title; this.content = content; } public void canNotAccess(){ System.out.println("抱歉,對於<"+title+">您沒有訪問權限!"); } public void canAccess(){ System.out.println("<"+title+">文件內容:"+content); } public Personnel getPersonnel() { return personnel; } } @Source() class PublicDocument extends Document{ public PublicDocument(Personnel personnel, String title, String content) { super(personnel, title, content); } } @Source(category = SourceCategory.Salary) class SalaryDocument extends Document{ public SalaryDocument(Personnel personnel, String title, String content) { super(personnel, title, content); } } @Source(category = SourceCategory.Achievement) class AchievementDocument extends Document{ public AchievementDocument(Personnel personnel, String title, String content) { super(personnel, title, content); } }
文件有內部私有屬性Personnal(如文件類型爲薪酬信息或業務統計則表示信息的所有者)、title(文件名稱)和content(文件內容),並實現了canAccess(訪問文件)和canNotAccess(無權限提示)方法;
接下來我們寫個demo來測試下我們的代碼:
public class Client { public static void main(String[] args) { List<Personnel> plist = new ArrayList<>(); Personnel boss = new Boss("boss"); Personnel manager = new Manager("manager"); Personnel zhangsan = new Employee("zhangsan"); Personnel lisi = new Employee("lisi"); Personnel accountant = new Accountant("accountant"); plist.add(boss); plist.add(manager); plist.add(zhangsan); plist.add(lisi); plist.add(accountant); List<Document> dlist = new ArrayList<>(); Document publicDocument = new PublicDocument(null, "十月一日放假公告", "十月一日放三天假"); Document managerSalary = new SalaryDocument(manager, "經理工資單", "工資30000/月"); Document zhangsanSalary = new SalaryDocument(zhangsan, "zhangsan工資單", "工資12000/月"); Document lisiSalary = new SalaryDocument(lisi, "lisi工資單", "工資8000/月"); Document accountantSalary = new SalaryDocument(accountant, "accountant工資單", "工資8000/月"); Document zhangsanAchievement = new AchievementDocument(zhangsan, "zhangsan業績", "200單/月"); Document lisiAchievement = new AchievementDocument(lisi, "lisi業績", "100單/月"); dlist.add(publicDocument); dlist.add(managerSalary); dlist.add(zhangsanSalary); dlist.add(lisiSalary); dlist.add(accountantSalary); dlist.add(zhangsanAchievement); dlist.add(lisiAchievement); for (Personnel personnel : plist) { System.out.println("當前訪問人:"+personnel.getName()); for (Document document : dlist) { personnel.access(document); } System.out.println("==================================="); } } }
先創建五個不同級別的訪問者,以及三種不同類型的共計7個文件;
運行結果:
當前訪問人:boss
<十月一日放假公告>文件內容:十月一日放三天假
<經理工資單>文件內容:工資30000/月
<zhangsan工資單>文件內容:工資12000/月
<lisi工資單>文件內容:工資8000/月
<accountant工資單>文件內容:工資8000/月
<zhangsan業績>文件內容:200單/月
<lisi業績>文件內容:100單/月
===================================
當前訪問人:manager
<十月一日放假公告>文件內容:十月一日放三天假
<經理工資單>文件內容:工資30000/月
抱歉,對於<zhangsan工資單>您沒有訪問權限!
抱歉,對於<lisi工資單>您沒有訪問權限!
抱歉,對於<accountant工資單>您沒有訪問權限!
<zhangsan業績>文件內容:200單/月
<lisi業績>文件內容:100單/月
===================================
當前訪問人:zhangsan
<十月一日放假公告>文件內容:十月一日放三天假
抱歉,對於<經理工資單>您沒有訪問權限!
<zhangsan工資單>文件內容:工資12000/月
抱歉,對於<lisi工資單>您沒有訪問權限!
抱歉,對於<accountant工資單>您沒有訪問權限!
<zhangsan業績>文件內容:200單/月
抱歉,對於<lisi業績>您沒有訪問權限!
===================================
當前訪問人:lisi
<十月一日放假公告>文件內容:十月一日放三天假
抱歉,對於<經理工資單>您沒有訪問權限!
抱歉,對於<zhangsan工資單>您沒有訪問權限!
<lisi工資單>文件內容:工資8000/月
抱歉,對於<accountant工資單>您沒有訪問權限!
抱歉,對於<zhangsan業績>您沒有訪問權限!
<lisi業績>文件內容:100單/月
===================================
當前訪問人:accountant
<十月一日放假公告>文件內容:十月一日放三天假
<經理工資單>文件內容:工資30000/月
<zhangsan工資單>文件內容:工資12000/月
<lisi工資單>文件內容:工資8000/月
<accountant工資單>文件內容:工資8000/月
抱歉,對於<zhangsan業績>您沒有訪問權限!
抱歉,對於<lisi業績>您沒有訪問權限!
===================================
所得結果符合我們的預期!
在開發過程中,遇見了兩個問題:
1.開始的時候並未編寫訪問者(Personnal)和文件(Document)的實現類而之編寫了父類
註解的類型並非使用@Target(ElementType.TYPE)而是@Target(ElementType.LOCAL_VARIABLE)
然後在demo中直接在創建實例的時候註解訪問者的權限級別和文件類型
@Level(level = PersonLevel.Boss) Personnel boss = new Boss("boss");
但是這種在運行時報java.lang.NullPointerException
原因是在調用訪問者的access訪問來訪問文件時,通過反射
Level level = this.getClass().getAnnotation(Level.class);
獲取的變量訪問權限級別註解爲null
而導致在調用的註解level()方法獲取註解值:訪問權限實例(理論上爲PersonLevel.Boss)候拋出空指針異常:
原因是:
目前的javac不會在bytecode中的local variable中保存annotation信息,所以就無法在runtime時獲取該annotaion。也就是說ElementType.LOCAL_VARIABLE只能用在RetentionPolicy.SOURCE情況下。
2.開始的時候我是把註解和枚舉寫在一起的
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Level { PersonLevel level() default PersonLevel.Employee; } enum PersonLevel{ Boss{ @Override boolean access(Personnel personnel, Document document) { return true; } }, Manager { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || category == SourceCategory.Achievement || personnel == document.getPersonnel(); } }, Employee { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || personnel == document.getPersonnel(); } }, Accountant { @Override boolean access(Personnel personnel, Document document) { SourceCategory category = document.getClass().getAnnotation(Source.class).category(); return category == SourceCategory.Public || category == SourceCategory.Salary || personnel == document.getPersonnel(); } },; abstract boolean access(Personnel personnel, Document document); }
這樣在運行時會報錯:
java.lang.IllegalAccessError: tried to access class package.PersonLevel from class com.sun.proxy.$Proxy1
代理類訪問不了PersonLevel,註解調用level()獲取訪問者權限枚舉的時候獲取不到,後來我把枚舉提出來變成public訪問修飾符此問題就解決了,多次測試發現,枚舉寫在註解類內部或者訪問修飾符是public纔可以,這個什麼原因希望有了解的大神可以指正!
本片文章就到這裏了,這是本人第一次分享編程知識,如有任何問題都希望能與本人交流指正。