註解和枚舉簡單實現訪問控制列表

實現之前,先簡單介紹下業務:

訪問控制列表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纔可以,這個什麼原因希望有了解的大神可以指正!

本片文章就到這裏了,這是本人第一次分享編程知識,如有任何問題都希望能與本人交流指正。

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