注解和枚举简单实现访问控制列表

实现之前,先简单介绍下业务:

访问控制列表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才可以,这个什么原因希望有了解的大神可以指正!

本片文章就到这里了,这是本人第一次分享编程知识,如有任何问题都希望能与本人交流指正。

猜你喜欢

转载自blog.csdn.net/ylwz0426cjy/article/details/80725319
今日推荐